Drop glgallery (#519)
* Neutralize GlGallery * Enable dataBinding and viewBinding * Add tachiyomi reader layout * Cleanup Dead-code * Rename .java to .kt * Fix activity rename * Nullability changes * Add basic tachiyomi reader imageviews * Fix size increase * Let's cleanup later * Add some tachiyomi webtoon widgets * Import tachiyomi webtoon pager without chapter transition * Remove rx dependency * Rewrite GalleryProvider interface with flow * groundwork for image load * Refine webtoon pageholder * Force us hw bitmap, refine recycle * Strip tachiyomi decoder usage * Keep our own SubsamplingScaleImageView, strip tachiyomi decoder to avoid apk bloated * Import other PageViewers * Add webtoon viewer config * groundFix ReaderActivity * Add tachiyomi Preferences * Readpreference * Fix fullscreen * Move related class to tachiyomi package * Fix pageload * Add readerconfig * Add ViewerConfig hook * Add Reader bottomsheet settings * Fix reader setting apply * Refine * Fix save share image * Refine theme
This commit is contained in:
1 parent
ca0518c747
commit
5213caac49
219 files changed
+11622
-18856
No files matched your search
@@ -0,0 +1,160 @@
|
||||
package com.davemorrissey.labs.subscaleview
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Rect
|
||||
import android.net.Uri
|
||||
import com.davemorrissey.labs.subscaleview.provider.AssetInputProvider
|
||||
import com.davemorrissey.labs.subscaleview.provider.InputProvider
|
||||
import com.davemorrissey.labs.subscaleview.provider.OpenStreamProvider
|
||||
import com.davemorrissey.labs.subscaleview.provider.ResourceInputProvider
|
||||
import com.davemorrissey.labs.subscaleview.provider.UriInputProvider
|
||||
import java.io.InputStream
|
||||
|
||||
/**
|
||||
* Helper class used to set the source and additional attributes from a variety of sources. Supports
|
||||
* use of a bitmap, asset, resource, external file or any other URI.
|
||||
*
|
||||
*
|
||||
* When you are using a preview image, you must set the dimensions of the full size image on the
|
||||
* ImageSource object for the full size image using the [.dimensions] method.
|
||||
*/
|
||||
class ImageSource {
|
||||
val bitmap: Bitmap?
|
||||
val provider: InputProvider?
|
||||
|
||||
private var sWidth = 0
|
||||
private var sHeight = 0
|
||||
|
||||
var sRegion = Rect(0, 0, 0, 0)
|
||||
private set
|
||||
var isCached = false
|
||||
private set
|
||||
|
||||
private constructor(bitmap: Bitmap, cached: Boolean) {
|
||||
this.bitmap = bitmap
|
||||
provider = null
|
||||
sWidth = bitmap.width
|
||||
sHeight = bitmap.height
|
||||
isCached = cached
|
||||
}
|
||||
|
||||
private constructor(provider: InputProvider) {
|
||||
bitmap = null
|
||||
this.provider = provider
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a region of the source image. Region must be set independently for the full size image and the preview if
|
||||
* you are using one.
|
||||
*
|
||||
* @param sRegion the region of the source image to be displayed.
|
||||
* @return this instance for chaining.
|
||||
*/
|
||||
fun region(sRegion: Rect): ImageSource {
|
||||
this.sRegion = sRegion
|
||||
setInvariants()
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare the dimensions of the image. This is only required for a full size image, when you are specifying a URI
|
||||
* and also a preview image. When displaying a bitmap object, or not using a preview, you do not need to declare
|
||||
* the image dimensions. Note if the declared dimensions are found to be incorrect, the view will reset.
|
||||
*
|
||||
* @param sWidth width of the source image.
|
||||
* @param sHeight height of the source image.
|
||||
* @return this instance for chaining.
|
||||
*/
|
||||
fun dimensions(sWidth: Int, sHeight: Int): ImageSource {
|
||||
if (bitmap == null) {
|
||||
this.sWidth = sWidth
|
||||
this.sHeight = sHeight
|
||||
}
|
||||
setInvariants()
|
||||
return this
|
||||
}
|
||||
|
||||
private fun setInvariants() {
|
||||
if (!sRegion.isEmpty) {
|
||||
sWidth = sRegion.width()
|
||||
sHeight = sRegion.height()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Create an instance from a resource. The correct resource for the device screen resolution will be used.
|
||||
*
|
||||
* @param resId resource ID.
|
||||
* @return an [ImageSource] instance.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun resource(context: Context, resId: Int): ImageSource {
|
||||
return ImageSource(ResourceInputProvider(context, resId))
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance from an asset name.
|
||||
*
|
||||
* @param assetName asset name.
|
||||
* @return an [ImageSource] instance.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun asset(context: Context, assetName: String): ImageSource {
|
||||
return ImageSource(AssetInputProvider(context, assetName))
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance from a URI.
|
||||
*
|
||||
* @param uri image URI.
|
||||
* @return an [ImageSource] instance.
|
||||
*/
|
||||
fun uri(context: Context, uri: Uri): ImageSource {
|
||||
return ImageSource(UriInputProvider(context, uri))
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance from an input provider.
|
||||
*
|
||||
* @param provider input stream provider.
|
||||
* @return an [ImageSource] instance.
|
||||
*/
|
||||
fun provider(provider: InputProvider): ImageSource {
|
||||
return ImageSource(provider)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance from an input stream.
|
||||
*
|
||||
* @param stream open input stream.
|
||||
* @return an [ImageSource] instance.
|
||||
*/
|
||||
fun inputStream(stream: InputStream): ImageSource {
|
||||
return ImageSource(OpenStreamProvider(stream))
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a loaded bitmap for display.
|
||||
*
|
||||
* @param bitmap bitmap to be displayed.
|
||||
* @return an [ImageSource] instance.
|
||||
*/
|
||||
fun bitmap(bitmap: Bitmap): ImageSource {
|
||||
return ImageSource(bitmap, false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a loaded and cached bitmap for display. This bitmap will not be recycled when it is no
|
||||
* longer needed. Use this method if you loaded the bitmap with an image loader such as Picasso
|
||||
* or Volley.
|
||||
*
|
||||
* @param bitmap bitmap to be displayed.
|
||||
* @return an [ImageSource] instance.
|
||||
*/
|
||||
fun cachedBitmap(bitmap: Bitmap): ImageSource {
|
||||
return ImageSource(bitmap, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.davemorrissey.labs.subscaleview
|
||||
|
||||
import android.graphics.PointF
|
||||
import java.io.Serializable
|
||||
|
||||
/**
|
||||
* Wraps the scale, center and orientation of a displayed image for easy restoration on screen rotate.
|
||||
*/
|
||||
class ImageViewState(val scale: Float, center: PointF) : Serializable {
|
||||
private val centerX: Float = center.x
|
||||
private val centerY: Float = center.y
|
||||
|
||||
val center: PointF
|
||||
get() = PointF(centerX, centerY)
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,53 @@
|
||||
package com.davemorrissey.labs.subscaleview.decoder
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Point
|
||||
import android.graphics.Rect
|
||||
import kotlin.Throws
|
||||
import com.davemorrissey.labs.subscaleview.provider.InputProvider
|
||||
import java.lang.Exception
|
||||
|
||||
/**
|
||||
* Interface for image decoding classes.
|
||||
*/
|
||||
interface Decoder {
|
||||
/**
|
||||
* Initialise the decoder. When possible, perform initial setup work once in this method. The
|
||||
* dimensions of the image must be returned.
|
||||
*
|
||||
* @param context Application context. A reference may be held, but must be cleared on recycle.
|
||||
* @param provider Provider of the image.
|
||||
* @return Dimensions of the image.
|
||||
* @throws Exception if initialisation fails.
|
||||
*/
|
||||
@Throws(Exception::class)
|
||||
fun init(context: Context, provider: InputProvider): Point
|
||||
|
||||
/**
|
||||
* Decode a region of the image with the given sample size. This method is called off the UI
|
||||
* thread so it can safely load the image on the current thread. It is called from
|
||||
* [android.os.AsyncTask]s running in an executor that may have multiple threads, so
|
||||
* implementations must be thread safe. Adding `synchronized` to the method signature
|
||||
* is the simplest way to achieve this, but bear in mind the [.recycle] method can be
|
||||
* called concurrently.
|
||||
*
|
||||
* @param sRect Source image rectangle to decode.
|
||||
* @param sampleSize Sample size.
|
||||
* @return The decoded region. It is safe to return null if decoding fails.
|
||||
*/
|
||||
fun decodeRegion(sRect: Rect, sampleSize: Int): Bitmap
|
||||
|
||||
/**
|
||||
* Status check. Should return false before initialisation and after recycle.
|
||||
*
|
||||
* @return true if the decoder is ready to be used.
|
||||
*/
|
||||
val isReady: Boolean
|
||||
|
||||
/**
|
||||
* This method will be called when the decoder is no longer required. It should clean up any
|
||||
* resources still in use.
|
||||
*/
|
||||
fun recycle()
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.davemorrissey.labs.subscaleview.decoder
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Point
|
||||
import android.graphics.Rect
|
||||
import kotlin.Throws
|
||||
import com.davemorrissey.labs.subscaleview.provider.InputProvider
|
||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||
import java.lang.Exception
|
||||
|
||||
class ImageDecoder @JvmOverloads constructor(
|
||||
private val cropBorders: Boolean,
|
||||
bitmapConfig: Bitmap.Config? = null
|
||||
) : Decoder {
|
||||
init {
|
||||
val globalBitmapConfig = SubsamplingScaleImageView.preferredBitmapConfig
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise the decoder. When possible, perform initial setup work once in this method. The
|
||||
* dimensions of the image must be returned.
|
||||
*
|
||||
* @param context Application context. A reference may be held, but must be cleared on recycle.
|
||||
* @param provider Provider of the image.
|
||||
* @return Dimensions of the image.
|
||||
* @throws Exception if initialisation fails.
|
||||
*/
|
||||
@Throws(Exception::class)
|
||||
override fun init(context: Context, provider: InputProvider): Point {
|
||||
TODO()
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a region of the image with the given sample size. This method is called off the UI
|
||||
* thread so it can safely load the image on the current thread. It is called from
|
||||
* [android.os.AsyncTask]s running in an executor that may have multiple threads, so
|
||||
* implementations must be thread safe. Adding `synchronized` to the method signature
|
||||
* is the simplest way to achieve this, but bear in mind the [.recycle] method can be
|
||||
* called concurrently.
|
||||
*
|
||||
* @param sRect Source image rectangle to decode.
|
||||
* @param sampleSize Sample size.
|
||||
* @return The decoded region. It is safe to return null if decoding fails.
|
||||
*/
|
||||
override fun decodeRegion(sRect: Rect, sampleSize: Int): Bitmap {
|
||||
TODO()
|
||||
}
|
||||
|
||||
/**
|
||||
* Status check. Should return false before initialisation and after recycle.
|
||||
*
|
||||
* @return true if the decoder is ready to be used.
|
||||
*/
|
||||
override val isReady: Boolean = TODO()
|
||||
|
||||
/**
|
||||
* This method will be called when the decoder is no longer required. It should clean up any
|
||||
* resources still in use.
|
||||
*/
|
||||
override fun recycle() {
|
||||
TODO()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.davemorrissey.labs.subscaleview.provider
|
||||
|
||||
import android.content.Context
|
||||
import java.io.IOException
|
||||
|
||||
class AssetInputProvider(context: Context, val assetName: String) : InputProvider {
|
||||
private val assets = context.assets
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun openStream() = assets.open(assetName)
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.davemorrissey.labs.subscaleview.provider
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import kotlin.Throws
|
||||
|
||||
interface InputProvider {
|
||||
@Throws(IOException::class)
|
||||
fun openStream(): InputStream?
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.davemorrissey.labs.subscaleview.provider
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import kotlin.Throws
|
||||
|
||||
/**
|
||||
* Provider from an already open input stream.
|
||||
*
|
||||
*
|
||||
* Please keep in mind this provider is not reusable.
|
||||
*/
|
||||
class OpenStreamProvider(private val stream: InputStream) : InputProvider {
|
||||
@Throws(IOException::class)
|
||||
override fun openStream() = stream
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package com.davemorrissey.labs.subscaleview.provider
|
||||
|
||||
import android.content.Context
|
||||
import java.io.IOException
|
||||
|
||||
class ResourceInputProvider(context: Context, val resource: Int) : InputProvider {
|
||||
private val resources = context.resources
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun openStream() = resources.openRawResource(resource)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.davemorrissey.labs.subscaleview.provider
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import java.io.IOException
|
||||
import kotlin.Throws
|
||||
|
||||
class UriInputProvider(context: Context, val uri: Uri) : InputProvider {
|
||||
private val resolver = context.contentResolver
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun openStream() = resolver.openInputStream(uri)
|
||||
}
|
||||
@@ -30,7 +30,6 @@ import com.hippo.ehviewer.client.EhConfig;
|
||||
import com.hippo.ehviewer.client.data.FavListUrlBuilder;
|
||||
import com.hippo.ehviewer.ui.CommonOperations;
|
||||
import com.hippo.ehviewer.ui.scene.GalleryListScene;
|
||||
import com.hippo.glgallery.GalleryView;
|
||||
import com.hippo.unifile.UniFile;
|
||||
import com.hippo.yorozuya.AssertUtils;
|
||||
import com.hippo.yorozuya.NumberUtils;
|
||||
@@ -131,11 +130,8 @@ public class Settings {
|
||||
private static final String KEY_SCREEN_ROTATION = "screen_rotation";
|
||||
private static final int DEFAULT_SCREEN_ROTATION = 0;
|
||||
private static final String KEY_READING_DIRECTION = "reading_direction";
|
||||
private static final int DEFAULT_READING_DIRECTION = GalleryView.LAYOUT_RIGHT_TO_LEFT;
|
||||
private static final String KEY_PAGE_SCALING = "page_scaling";
|
||||
private static final int DEFAULT_PAGE_SCALING = GalleryView.SCALE_FIT;
|
||||
private static final String KEY_START_POSITION = "start_position";
|
||||
private static final int DEFAULT_START_POSITION = GalleryView.START_POSITION_TOP_RIGHT;
|
||||
private static final String KEY_KEEP_SCREEN_ON = "keep_screen_on";
|
||||
private static final boolean DEFAULT_KEEP_SCREEN_ON = false;
|
||||
private static final String KEY_SHOW_CLOCK = "gallery_show_clock";
|
||||
@@ -236,7 +232,7 @@ public class Settings {
|
||||
public static boolean LIST_THUMB_SIZE_INITED = false;
|
||||
private static int LIST_THUMB_SIZE = 40;
|
||||
private static Context sContext;
|
||||
private static SharedPreferences sSettingsPre;
|
||||
public static SharedPreferences sSettingsPre;
|
||||
private static EhConfig sEhConfig;
|
||||
|
||||
public static void initialize(Context context) {
|
||||
@@ -507,29 +503,14 @@ public class Settings {
|
||||
putIntToStr(KEY_SCREEN_ROTATION, value);
|
||||
}
|
||||
|
||||
@GalleryView.LayoutMode
|
||||
public static int getReadingDirection() {
|
||||
return GalleryView.sanitizeLayoutMode(getIntFromStr(KEY_READING_DIRECTION, DEFAULT_READING_DIRECTION));
|
||||
}
|
||||
|
||||
public static void putReadingDirection(int value) {
|
||||
putIntToStr(KEY_READING_DIRECTION, value);
|
||||
}
|
||||
|
||||
@GalleryView.ScaleMode
|
||||
public static int getPageScaling() {
|
||||
return GalleryView.sanitizeScaleMode(getIntFromStr(KEY_PAGE_SCALING, DEFAULT_PAGE_SCALING));
|
||||
}
|
||||
|
||||
public static void putPageScaling(int value) {
|
||||
putIntToStr(KEY_PAGE_SCALING, value);
|
||||
}
|
||||
|
||||
@GalleryView.StartPosition
|
||||
public static int getStartPosition() {
|
||||
return GalleryView.sanitizeStartPosition(getIntFromStr(KEY_START_POSITION, DEFAULT_START_POSITION));
|
||||
}
|
||||
|
||||
public static void putStartPosition(int value) {
|
||||
putIntToStr(KEY_START_POSITION, value);
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ import androidx.browser.customtabs.CustomTabsIntent;
|
||||
import com.hippo.ehviewer.client.EhUrlOpener;
|
||||
import com.hippo.ehviewer.client.data.GalleryDetail;
|
||||
import com.hippo.ehviewer.client.parser.GalleryPageUrlParser;
|
||||
import com.hippo.ehviewer.ui.GalleryActivity;
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
|
||||
import com.hippo.ehviewer.ui.MainActivity;
|
||||
import com.hippo.scene.Announcer;
|
||||
import com.hippo.scene.StageActivity;
|
||||
@@ -56,19 +56,19 @@ public final class UrlOpener {
|
||||
GalleryPageUrlParser.Result result = GalleryPageUrlParser.parse(url);
|
||||
if (result != null) {
|
||||
if (result.gid == galleryDetail.gid) {
|
||||
intent = new Intent(context, GalleryActivity.class);
|
||||
intent.setAction(GalleryActivity.ACTION_EH);
|
||||
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, galleryDetail);
|
||||
intent.putExtra(GalleryActivity.KEY_PAGE, result.page);
|
||||
intent = new Intent(context, ReaderActivity.class);
|
||||
intent.setAction(ReaderActivity.ACTION_EH);
|
||||
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, galleryDetail);
|
||||
intent.putExtra(ReaderActivity.KEY_PAGE, result.page);
|
||||
context.startActivity(intent);
|
||||
return;
|
||||
}
|
||||
} else if (url.startsWith("#c")) {
|
||||
try {
|
||||
intent = new Intent(context, GalleryActivity.class);
|
||||
intent.setAction(GalleryActivity.ACTION_EH);
|
||||
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, galleryDetail);
|
||||
intent.putExtra(GalleryActivity.KEY_PAGE, Integer.parseInt(url.replace("#c", "")) - 1);
|
||||
intent = new Intent(context, ReaderActivity.class);
|
||||
intent.setAction(ReaderActivity.ACTION_EH);
|
||||
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, galleryDetail);
|
||||
intent.putExtra(ReaderActivity.KEY_PAGE, Integer.parseInt(url.replace("#c", "")) - 1);
|
||||
context.startActivity(intent);
|
||||
return;
|
||||
} catch (NumberFormatException e) {
|
||||
|
||||
+8
-9
@@ -46,7 +46,9 @@ import java.util.Set;
|
||||
import java.util.Stack;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
import eu.kanade.tachiyomi.ui.reader.loader.PageLoader;
|
||||
|
||||
public class ArchivePageLoader extends PageLoader2 {
|
||||
private static final AtomicInteger sIdGenerator = new AtomicInteger();
|
||||
public static Handler showPasswd;
|
||||
public static String passwd;
|
||||
@@ -61,10 +63,10 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
new Thread(new DecodeTask())
|
||||
};
|
||||
private Thread archiveThread;
|
||||
private volatile int size = STATE_WAIT;
|
||||
private volatile int size = PageLoader.STATE_WAIT;
|
||||
private String error;
|
||||
|
||||
public ArchiveGalleryProvider(Context context, Uri uri) {
|
||||
public ArchivePageLoader(Context context, Uri uri) {
|
||||
UriArchiveAccessor archiveAccessor1;
|
||||
try {
|
||||
archiveAccessor1 = new UriArchiveAccessor(context, uri);
|
||||
@@ -77,8 +79,6 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
super.start();
|
||||
|
||||
int id = sIdGenerator.incrementAndGet();
|
||||
|
||||
archiveThread = new PriorityThread(
|
||||
@@ -91,7 +91,6 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
super.stop();
|
||||
if (archiveThread != null) {
|
||||
archiveThread.interrupt();
|
||||
archiveThread = null;
|
||||
@@ -217,7 +216,7 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
size = 0;
|
||||
}
|
||||
if (size <= 0) {
|
||||
size = STATE_ERROR;
|
||||
size = PageLoader.STATE_ERROR;
|
||||
error = GetText.getString(R.string.error_reading_failed);
|
||||
notifyDataChanged();
|
||||
return;
|
||||
@@ -289,7 +288,7 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
|
||||
// Check index valid
|
||||
if (index < 0 || index >= size) {
|
||||
notifyPageFailed(index, GetText.getString(R.string.error_out_of_range));
|
||||
notifyPageFailed(index);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -347,7 +346,7 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
if (image != null) {
|
||||
notifyPageSucceed(index, image);
|
||||
} else {
|
||||
notifyPageFailed(index, GetText.getString(R.string.error_decoding_failed));
|
||||
notifyPageFailed(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
-17
@@ -24,7 +24,6 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.ehviewer.GetText;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.glgallery.GalleryPageView;
|
||||
import com.hippo.image.Image;
|
||||
import com.hippo.unifile.FilenameFilter;
|
||||
import com.hippo.unifile.UniFile;
|
||||
@@ -44,9 +43,11 @@ import java.util.Stack;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public class DirGalleryProvider extends GalleryProvider2 implements Runnable {
|
||||
import eu.kanade.tachiyomi.ui.reader.loader.PageLoader;
|
||||
|
||||
private static final String TAG = DirGalleryProvider.class.getSimpleName();
|
||||
public class DirPageLoader extends PageLoader2 implements Runnable {
|
||||
|
||||
private static final String TAG = DirPageLoader.class.getSimpleName();
|
||||
private static final AtomicInteger sIdGenerator = new AtomicInteger();
|
||||
private static final FilenameFilter imageFilter =
|
||||
(dir, name) -> StringUtils.endsWith(name.toLowerCase(), SUPPORT_IMAGE_EXTENSIONS);
|
||||
@@ -60,21 +61,19 @@ public class DirGalleryProvider extends GalleryProvider2 implements Runnable {
|
||||
};
|
||||
private final UniFile mDir;
|
||||
private final Stack<Integer> mRequests = new Stack<>();
|
||||
private final AtomicInteger mDecodingIndex = new AtomicInteger(GalleryPageView.INVALID_INDEX);
|
||||
private final AtomicInteger mDecodingIndex = new AtomicInteger(-1);
|
||||
private final AtomicReference<UniFile[]> mFileList = new AtomicReference<>();
|
||||
@Nullable
|
||||
private Thread mBgThread;
|
||||
private volatile int mSize = STATE_WAIT;
|
||||
private volatile int mSize = PageLoader.STATE_WAIT;
|
||||
private String mError;
|
||||
|
||||
public DirGalleryProvider(@NonNull UniFile dir) {
|
||||
public DirPageLoader(@NonNull UniFile dir) {
|
||||
mDir = dir;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
super.start();
|
||||
|
||||
mBgThread = new PriorityThread(this, TAG + '-' + sIdGenerator.incrementAndGet(),
|
||||
Process.THREAD_PRIORITY_BACKGROUND);
|
||||
mBgThread.start();
|
||||
@@ -82,8 +81,6 @@ public class DirGalleryProvider extends GalleryProvider2 implements Runnable {
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
super.stop();
|
||||
|
||||
if (mBgThread != null) {
|
||||
mBgThread.interrupt();
|
||||
mBgThread = null;
|
||||
@@ -213,7 +210,7 @@ public class DirGalleryProvider extends GalleryProvider2 implements Runnable {
|
||||
UniFile[] files = mDir.listFiles(imageFilter);
|
||||
|
||||
if (files == null) {
|
||||
mSize = STATE_ERROR;
|
||||
mSize = PageLoader.STATE_ERROR;
|
||||
mError = GetText.getString(R.string.error_not_folder_path);
|
||||
|
||||
// Notify to to show error
|
||||
@@ -251,8 +248,8 @@ public class DirGalleryProvider extends GalleryProvider2 implements Runnable {
|
||||
|
||||
// Check index valid
|
||||
if (index < 0 || index >= files.length) {
|
||||
mDecodingIndex.lazySet(GalleryPageView.INVALID_INDEX);
|
||||
notifyPageFailed(index, GetText.getString(R.string.error_out_of_range));
|
||||
mDecodingIndex.lazySet(-1);
|
||||
notifyPageFailed(index);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -260,15 +257,15 @@ public class DirGalleryProvider extends GalleryProvider2 implements Runnable {
|
||||
try {
|
||||
is = files[index].openInputStream();
|
||||
Image image = Image.decode((FileInputStream) is);
|
||||
mDecodingIndex.lazySet(GalleryPageView.INVALID_INDEX);
|
||||
mDecodingIndex.lazySet(-1);
|
||||
notifyPageSucceed(index, image);
|
||||
} catch (IOException e) {
|
||||
mDecodingIndex.lazySet(GalleryPageView.INVALID_INDEX);
|
||||
notifyPageFailed(index, GetText.getString(R.string.error_reading_failed));
|
||||
mDecodingIndex.lazySet(-1);
|
||||
notifyPageFailed(index);
|
||||
} finally {
|
||||
IOUtils.closeQuietly(is);
|
||||
}
|
||||
mDecodingIndex.lazySet(GalleryPageView.INVALID_INDEX);
|
||||
mDecodingIndex.lazySet(-1);
|
||||
}
|
||||
|
||||
// Clear file list
|
||||
+10
-12
@@ -23,37 +23,35 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo;
|
||||
import com.hippo.ehviewer.spider.SpiderQueen;
|
||||
import com.hippo.glgallery.GalleryProvider;
|
||||
import eu.kanade.tachiyomi.ui.reader.loader.PageLoader;
|
||||
import com.hippo.image.Image;
|
||||
import com.hippo.unifile.UniFile;
|
||||
import com.hippo.yorozuya.SimpleHandler;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class EhGalleryProvider extends GalleryProvider2 implements SpiderQueen.OnSpiderListener {
|
||||
public class EhPageLoader extends PageLoader2 implements SpiderQueen.OnSpiderListener {
|
||||
|
||||
private final Context mContext;
|
||||
private final GalleryInfo mGalleryInfo;
|
||||
@Nullable
|
||||
private SpiderQueen mSpiderQueen;
|
||||
|
||||
public EhGalleryProvider(Context context, GalleryInfo galleryInfo) {
|
||||
public EhPageLoader(Context context, GalleryInfo galleryInfo) {
|
||||
mContext = context;
|
||||
mGalleryInfo = galleryInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
super.start();
|
||||
|
||||
mSpiderQueen = SpiderQueen.obtainSpiderQueen(mContext, mGalleryInfo, SpiderQueen.MODE_READ);
|
||||
mSpiderQueen.addOnSpiderListener(this);
|
||||
if (mSpiderQueen.size() > 0)
|
||||
notifyDataChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
super.stop();
|
||||
|
||||
if (mSpiderQueen != null) {
|
||||
mSpiderQueen.removeOnSpiderListener(this);
|
||||
// Activity recreate may called, so wait 3000s
|
||||
@@ -120,7 +118,7 @@ public class EhGalleryProvider extends GalleryProvider2 implements SpiderQueen.O
|
||||
if (mSpiderQueen != null) {
|
||||
return mSpiderQueen.size();
|
||||
} else {
|
||||
return GalleryProvider.STATE_ERROR;
|
||||
return PageLoader.STATE_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +129,7 @@ public class EhGalleryProvider extends GalleryProvider2 implements SpiderQueen.O
|
||||
if (object instanceof Float) {
|
||||
notifyPagePercent(index, (Float) object);
|
||||
} else if (object instanceof String) {
|
||||
notifyPageFailed(index, (String) object);
|
||||
notifyPageFailed(index);
|
||||
} else if (object == null) {
|
||||
notifyPageWait(index);
|
||||
}
|
||||
@@ -145,7 +143,7 @@ public class EhGalleryProvider extends GalleryProvider2 implements SpiderQueen.O
|
||||
if (object instanceof Float) {
|
||||
notifyPagePercent(index, (Float) object);
|
||||
} else if (object instanceof String) {
|
||||
notifyPageFailed(index, (String) object);
|
||||
notifyPageFailed(index);
|
||||
} else if (object == null) {
|
||||
notifyPageWait(index);
|
||||
}
|
||||
@@ -192,7 +190,7 @@ public class EhGalleryProvider extends GalleryProvider2 implements SpiderQueen.O
|
||||
|
||||
@Override
|
||||
public void onPageFailure(int index, String error, int finished, int downloaded, int total) {
|
||||
notifyPageFailed(index, error);
|
||||
notifyPageFailed(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -206,7 +204,7 @@ public class EhGalleryProvider extends GalleryProvider2 implements SpiderQueen.O
|
||||
|
||||
@Override
|
||||
public void onGetImageFailure(int index, String error) {
|
||||
notifyPageFailed(index, error);
|
||||
notifyPageFailed(index);
|
||||
}
|
||||
|
||||
private static class ReleaseTask implements Runnable {
|
||||
+2
-2
@@ -19,10 +19,10 @@ package com.hippo.ehviewer.gallery;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.glgallery.GalleryProvider;
|
||||
import eu.kanade.tachiyomi.ui.reader.loader.PageLoader;
|
||||
import com.hippo.unifile.UniFile;
|
||||
|
||||
public abstract class GalleryProvider2 extends GalleryProvider {
|
||||
public abstract class PageLoader2 extends PageLoader {
|
||||
|
||||
// With dot
|
||||
public static final String[] SUPPORT_IMAGE_EXTENSIONS = {
|
||||
@@ -28,7 +28,7 @@ import com.hippo.ehviewer.Settings;
|
||||
import com.hippo.ehviewer.client.EhCacheKeyFactory;
|
||||
import com.hippo.ehviewer.client.EhUtils;
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo;
|
||||
import com.hippo.ehviewer.gallery.GalleryProvider2;
|
||||
import com.hippo.ehviewer.gallery.PageLoader2;
|
||||
import com.hippo.io.UniFileInputStreamPipe;
|
||||
import com.hippo.io.UniFileOutputStreamPipe;
|
||||
import com.hippo.streampipe.InputStreamPipe;
|
||||
@@ -118,7 +118,7 @@ public final class SpiderDen {
|
||||
|
||||
@Nullable
|
||||
private static UniFile findImageFile(UniFile dir, int index) {
|
||||
for (String extension : GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS) {
|
||||
for (String extension : PageLoader2.SUPPORT_IMAGE_EXTENSIONS) {
|
||||
String filename = generateImageFilename(index, extension);
|
||||
UniFile file = dir.findFile(filename);
|
||||
if (file != null) {
|
||||
@@ -179,10 +179,10 @@ public final class SpiderDen {
|
||||
* @param extension with dot
|
||||
*/
|
||||
private String fixExtension(String extension) {
|
||||
if (Utilities.contain(GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS, extension)) {
|
||||
if (Utilities.contain(PageLoader2.SUPPORT_IMAGE_EXTENSIONS, extension)) {
|
||||
return extension;
|
||||
} else {
|
||||
return GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS[0];
|
||||
return PageLoader2.SUPPORT_IMAGE_EXTENSIONS[0];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,8 +261,8 @@ public final class SpiderDen {
|
||||
}
|
||||
|
||||
boolean result = false;
|
||||
for (int i = 0, n = GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS.length; i < n; i++) {
|
||||
String filename = generateImageFilename(index, GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS[i]);
|
||||
for (int i = 0, n = PageLoader2.SUPPORT_IMAGE_EXTENSIONS.length; i < n; i++) {
|
||||
String filename = generateImageFilename(index, PageLoader2.SUPPORT_IMAGE_EXTENSIONS[i]);
|
||||
UniFile file = dir.subFile(filename);
|
||||
if (file != null) {
|
||||
result |= file.delete();
|
||||
|
||||
@@ -48,9 +48,8 @@ import com.hippo.ehviewer.client.parser.GalleryMultiPageViewerPTokenParser;
|
||||
import com.hippo.ehviewer.client.parser.GalleryPageApiParser;
|
||||
import com.hippo.ehviewer.client.parser.GalleryPageParser;
|
||||
import com.hippo.ehviewer.client.parser.GalleryPageUrlParser;
|
||||
import com.hippo.ehviewer.gallery.GalleryProvider2;
|
||||
import com.hippo.glgallery.GalleryPageView;
|
||||
import com.hippo.glgallery.GalleryProvider;
|
||||
import com.hippo.ehviewer.gallery.PageLoader2;
|
||||
import eu.kanade.tachiyomi.ui.reader.loader.PageLoader;
|
||||
import com.hippo.image.Image;
|
||||
import com.hippo.streampipe.InputStreamPipe;
|
||||
import com.hippo.streampipe.OutputStreamPipe;
|
||||
@@ -167,7 +166,7 @@ public final class SpiderQueen implements Runnable {
|
||||
mPreloadNumber = MathUtils.clamp(Settings.getPreloadImage(), 0, 100);
|
||||
|
||||
for (int i = 0; i < DECODE_THREAD_NUM; i++) {
|
||||
mDecodeIndexArray[i] = GalleryPageView.INVALID_INDEX;
|
||||
mDecodeIndexArray[i] = -1;
|
||||
}
|
||||
|
||||
mWorkerPoolExecutor = new ThreadPoolExecutor(mWorkerMaxCount, mWorkerMaxCount,
|
||||
@@ -399,9 +398,9 @@ public final class SpiderQueen implements Runnable {
|
||||
|
||||
public int size() {
|
||||
if (mQueenThread == null) {
|
||||
return GalleryProvider.STATE_ERROR;
|
||||
return PageLoader.STATE_ERROR;
|
||||
} else if (mPageStateArray == null) {
|
||||
return GalleryProvider.STATE_WAIT;
|
||||
return PageLoader.STATE_WAIT;
|
||||
} else {
|
||||
return mPageStateArray.length;
|
||||
}
|
||||
@@ -1274,8 +1273,8 @@ public final class SpiderQueen implements Runnable {
|
||||
extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mediaType.toString());
|
||||
}
|
||||
// Ensure extension
|
||||
if (!Utilities.contain(GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS, extension)) {
|
||||
extension = GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS[0];
|
||||
if (!Utilities.contain(PageLoader2.SUPPORT_IMAGE_EXTENSIONS, extension)) {
|
||||
extension = PageLoader2.SUPPORT_IMAGE_EXTENSIONS[0];
|
||||
}
|
||||
|
||||
OutputStreamPipe osPipe = null;
|
||||
@@ -1596,7 +1595,7 @@ public final class SpiderQueen implements Runnable {
|
||||
|
||||
private void resetDecodeIndex() {
|
||||
synchronized (mDecodeRequestQueue) {
|
||||
mDecodeIndexArray[mThreadIndex] = GalleryPageView.INVALID_INDEX;
|
||||
mDecodeIndexArray[mThreadIndex] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -70,7 +70,7 @@ import com.hippo.ehviewer.dao.DownloadLabel;
|
||||
import com.hippo.ehviewer.download.DownloadManager;
|
||||
import com.hippo.ehviewer.download.DownloadService;
|
||||
import com.hippo.ehviewer.spider.SpiderDen;
|
||||
import com.hippo.ehviewer.ui.GalleryActivity;
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
|
||||
import com.hippo.ehviewer.widget.SimpleRatingView;
|
||||
import com.hippo.scene.Announcer;
|
||||
import com.hippo.unifile.UniFile;
|
||||
@@ -596,9 +596,9 @@ public class DownloadsScene extends ToolbarScene
|
||||
return false;
|
||||
}
|
||||
|
||||
Intent intent = new Intent(activity, GalleryActivity.class);
|
||||
intent.setAction(GalleryActivity.ACTION_EH);
|
||||
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, list.get(position));
|
||||
Intent intent = new Intent(activity, ReaderActivity.class);
|
||||
intent.setAction(ReaderActivity.ACTION_EH);
|
||||
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, list.get(position));
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -89,11 +89,11 @@ import com.hippo.ehviewer.client.parser.RateGalleryParser;
|
||||
import com.hippo.ehviewer.client.parser.VoteTagParser;
|
||||
import com.hippo.ehviewer.dao.DownloadInfo;
|
||||
import com.hippo.ehviewer.dao.Filter;
|
||||
import com.hippo.ehviewer.gallery.EhGalleryProvider;
|
||||
import com.hippo.ehviewer.gallery.GalleryProvider2;
|
||||
import com.hippo.ehviewer.gallery.EhPageLoader;
|
||||
import com.hippo.ehviewer.gallery.PageLoader2;
|
||||
import com.hippo.ehviewer.spider.SpiderDen;
|
||||
import com.hippo.ehviewer.ui.CommonOperations;
|
||||
import com.hippo.ehviewer.ui.GalleryActivity;
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
|
||||
import com.hippo.ehviewer.ui.GalleryInfoBottomSheet;
|
||||
import com.hippo.ehviewer.ui.MainActivity;
|
||||
import com.hippo.ehviewer.ui.annotation.WholeLifeCircle;
|
||||
@@ -452,7 +452,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
super.onResume();
|
||||
if (mRead != null) {
|
||||
try {
|
||||
GalleryProvider2 galleryProvider = new EhGalleryProvider(requireContext(), mGalleryInfo);
|
||||
PageLoader2 galleryProvider = new EhPageLoader(requireContext(), mGalleryInfo);
|
||||
galleryProvider.start();
|
||||
int startPage = galleryProvider.getStartPage();
|
||||
if (startPage != 0) {
|
||||
@@ -866,10 +866,10 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
if (mPage != 0) {
|
||||
Snackbar.make(requireActivity().findViewById(R.id.snackbar), getString(R.string.read_from, mPage), Snackbar.LENGTH_LONG)
|
||||
.setAction(R.string.read, v -> {
|
||||
Intent intent = new Intent(requireContext(), GalleryActivity.class);
|
||||
intent.setAction(GalleryActivity.ACTION_EH);
|
||||
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, mGalleryDetail);
|
||||
intent.putExtra(GalleryActivity.KEY_PAGE, mPage);
|
||||
Intent intent = new Intent(requireContext(), ReaderActivity.class);
|
||||
intent.setAction(ReaderActivity.ACTION_EH);
|
||||
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, mGalleryDetail);
|
||||
intent.putExtra(ReaderActivity.KEY_PAGE, mPage);
|
||||
startActivity(intent);
|
||||
})
|
||||
.show();
|
||||
@@ -1164,9 +1164,9 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
galleryInfo = mGalleryDetail;
|
||||
}
|
||||
if (galleryInfo != null) {
|
||||
Intent intent = new Intent(activity, GalleryActivity.class);
|
||||
intent.setAction(GalleryActivity.ACTION_EH);
|
||||
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, galleryInfo);
|
||||
Intent intent = new Intent(activity, ReaderActivity.class);
|
||||
intent.setAction(ReaderActivity.ACTION_EH);
|
||||
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, galleryInfo);
|
||||
startActivity(intent);
|
||||
}
|
||||
} else if (mNewerVersion == v) {
|
||||
@@ -1298,10 +1298,10 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
o = v.getTag(R.id.index);
|
||||
if (null != galleryInfo && o instanceof Integer) {
|
||||
int index = (Integer) o;
|
||||
Intent intent = new Intent(context, GalleryActivity.class);
|
||||
intent.setAction(GalleryActivity.ACTION_EH);
|
||||
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, galleryInfo);
|
||||
intent.putExtra(GalleryActivity.KEY_PAGE, index);
|
||||
Intent intent = new Intent(context, ReaderActivity.class);
|
||||
intent.setAction(ReaderActivity.ACTION_EH);
|
||||
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, galleryInfo);
|
||||
intent.putExtra(ReaderActivity.KEY_PAGE, index);
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ import com.hippo.ehviewer.dao.DownloadLabel;
|
||||
import com.hippo.ehviewer.dao.QuickSearch;
|
||||
import com.hippo.ehviewer.download.DownloadManager;
|
||||
import com.hippo.ehviewer.ui.CommonOperations;
|
||||
import com.hippo.ehviewer.ui.GalleryActivity;
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
|
||||
import com.hippo.ehviewer.ui.MainActivity;
|
||||
import com.hippo.ehviewer.ui.dialog.SelectItemWithIconAdapter;
|
||||
import com.hippo.ehviewer.widget.GalleryInfoContentHelper;
|
||||
@@ -977,9 +977,9 @@ public final class GalleryListScene extends SearchBarScene
|
||||
.setAdapter(new SelectItemWithIconAdapter(context, items, icons), (dialog, which) -> {
|
||||
switch (which) {
|
||||
case 0: // Read
|
||||
Intent intent = new Intent(activity, GalleryActivity.class);
|
||||
intent.setAction(GalleryActivity.ACTION_EH);
|
||||
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, gi);
|
||||
Intent intent = new Intent(activity, ReaderActivity.class);
|
||||
intent.setAction(ReaderActivity.ACTION_EH);
|
||||
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, gi);
|
||||
startActivity(intent);
|
||||
break;
|
||||
case 1: // Download
|
||||
|
||||
@@ -49,7 +49,7 @@ import com.hippo.ehviewer.client.data.GalleryInfo;
|
||||
import com.hippo.ehviewer.client.data.GalleryPreview;
|
||||
import com.hippo.ehviewer.client.data.PreviewSet;
|
||||
import com.hippo.ehviewer.client.exception.EhException;
|
||||
import com.hippo.ehviewer.ui.GalleryActivity;
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
|
||||
import com.hippo.ehviewer.ui.MainActivity;
|
||||
import com.hippo.scene.SceneFragment;
|
||||
import com.hippo.widget.ContentLayout;
|
||||
@@ -227,10 +227,10 @@ public class GalleryPreviewsScene extends ToolbarScene {
|
||||
if (null != context && null != mHelper && null != mGalleryInfo) {
|
||||
GalleryPreview p = mHelper.getDataAtEx(position);
|
||||
if (p != null) {
|
||||
Intent intent = new Intent(context, GalleryActivity.class);
|
||||
intent.setAction(GalleryActivity.ACTION_EH);
|
||||
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, mGalleryInfo);
|
||||
intent.putExtra(GalleryActivity.KEY_PAGE, p.getPosition());
|
||||
Intent intent = new Intent(context, ReaderActivity.class);
|
||||
intent.setAction(ReaderActivity.ACTION_EH);
|
||||
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, mGalleryInfo);
|
||||
intent.putExtra(ReaderActivity.KEY_PAGE, p.getPosition());
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ import com.hippo.ehviewer.dao.DownloadInfo
|
||||
import com.hippo.ehviewer.dao.HistoryInfo
|
||||
import com.hippo.ehviewer.download.DownloadManager.DownloadInfoListener
|
||||
import com.hippo.ehviewer.ui.CommonOperations
|
||||
import com.hippo.ehviewer.ui.GalleryActivity
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||
import com.hippo.ehviewer.ui.dialog.SelectItemWithIconAdapter
|
||||
import com.hippo.ehviewer.widget.SimpleRatingView
|
||||
import com.hippo.scene.Announcer
|
||||
@@ -267,9 +267,9 @@ class HistoryScene : ToolbarScene() {
|
||||
) { _: DialogInterface?, which: Int ->
|
||||
when (which) {
|
||||
0 -> {
|
||||
val intent = Intent(activity, GalleryActivity::class.java)
|
||||
intent.action = GalleryActivity.ACTION_EH
|
||||
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, gi)
|
||||
val intent = Intent(activity, ReaderActivity::class.java)
|
||||
intent.action = ReaderActivity.ACTION_EH
|
||||
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, gi)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,202 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.Settings;
|
||||
import com.hippo.yorozuya.ViewUtils;
|
||||
|
||||
public class GalleryGuideView extends ViewGroup implements View.OnClickListener {
|
||||
|
||||
private final float[] mPoints = new float[3 * 4];
|
||||
private int mBgColor;
|
||||
private Paint mPaint;
|
||||
private int mStep;
|
||||
|
||||
private TextView mLeftText;
|
||||
private TextView mRightText;
|
||||
private TextView mMenuText;
|
||||
private TextView mProgressText;
|
||||
private TextView mLongClickText;
|
||||
|
||||
public GalleryGuideView(Context context) {
|
||||
super(context);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public GalleryGuideView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public GalleryGuideView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context);
|
||||
}
|
||||
|
||||
private void init(Context context) {
|
||||
mBgColor = Color.GRAY;
|
||||
mPaint = new Paint();
|
||||
mPaint.setColor(Color.WHITE);
|
||||
mPaint.setStyle(Paint.Style.STROKE);
|
||||
mPaint.setStrokeWidth(context.getResources().getDimension(R.dimen.gallery_guide_divider_width));
|
||||
setOnClickListener(this);
|
||||
setWillNotDraw(false);
|
||||
bind();
|
||||
}
|
||||
|
||||
private void bind() {
|
||||
// Clear up
|
||||
removeAllViews();
|
||||
mLeftText = null;
|
||||
mRightText = null;
|
||||
mMenuText = null;
|
||||
mProgressText = null;
|
||||
mLongClickText = null;
|
||||
|
||||
switch (mStep) {
|
||||
case 0:
|
||||
bind1();
|
||||
break;
|
||||
default:
|
||||
case 1:
|
||||
bind2();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void bind1() {
|
||||
LayoutInflater inflater = LayoutInflater.from(getContext());
|
||||
inflater.inflate(R.layout.widget_gallery_guide_1, this);
|
||||
mLeftText = (TextView) getChildAt(0);
|
||||
mRightText = (TextView) getChildAt(1);
|
||||
mMenuText = (TextView) getChildAt(2);
|
||||
mProgressText = (TextView) getChildAt(3);
|
||||
}
|
||||
|
||||
private void bind2() {
|
||||
LayoutInflater inflater = LayoutInflater.from(getContext());
|
||||
inflater.inflate(R.layout.widget_gallery_guide_2, this);
|
||||
mLongClickText = (TextView) getChildAt(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
|
||||
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
|
||||
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
|
||||
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
|
||||
if (MeasureSpec.EXACTLY != widthMode || MeasureSpec.EXACTLY != heightMode) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
switch (mStep) {
|
||||
case 0:
|
||||
mLeftText.measure(MeasureSpec.makeMeasureSpec(widthSize / 3, MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY));
|
||||
mRightText.measure(MeasureSpec.makeMeasureSpec(widthSize / 3, MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY));
|
||||
mMenuText.measure(MeasureSpec.makeMeasureSpec(widthSize / 3, MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(heightSize / 2, MeasureSpec.EXACTLY));
|
||||
mProgressText.measure(MeasureSpec.makeMeasureSpec(widthSize / 3, MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(heightSize / 2, MeasureSpec.EXACTLY));
|
||||
break;
|
||||
default:
|
||||
case 1:
|
||||
mLongClickText.measure(MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY));
|
||||
break;
|
||||
}
|
||||
|
||||
setMeasuredDimension(widthSize, heightSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
int width = r - l;
|
||||
int height = b - t;
|
||||
|
||||
switch (mStep) {
|
||||
case 0:
|
||||
mLeftText.layout(0, 0, width / 3, height);
|
||||
mRightText.layout(width * 2 / 3, 0, width, height);
|
||||
mMenuText.layout(width / 3, 0, width * 2 / 3, height / 2);
|
||||
mProgressText.layout(width / 3, height / 2, width * 2 / 3, height);
|
||||
break;
|
||||
default:
|
||||
case 1:
|
||||
mLongClickText.layout(0, 0, width, height);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
|
||||
if (0 == mStep) {
|
||||
mPoints[0] = w / 3;
|
||||
mPoints[1] = 0;
|
||||
mPoints[2] = w / 3;
|
||||
mPoints[3] = h;
|
||||
|
||||
mPoints[4] = w * 2 / 3;
|
||||
mPoints[5] = 0;
|
||||
mPoints[6] = w * 2 / 3;
|
||||
mPoints[7] = h;
|
||||
|
||||
mPoints[8] = w / 3;
|
||||
mPoints[9] = h / 2;
|
||||
mPoints[10] = w * 2 / 3;
|
||||
mPoints[11] = h / 2;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
canvas.drawColor(mBgColor);
|
||||
if (0 == mStep) {
|
||||
canvas.drawLines(mPoints, mPaint);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
switch (mStep) {
|
||||
case 0:
|
||||
mStep++;
|
||||
bind();
|
||||
break;
|
||||
default:
|
||||
case 1:
|
||||
Settings.putGuideGallery(false);
|
||||
ViewUtils.removeFromParent(this);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,210 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.core.view.DisplayCutoutCompat;
|
||||
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.yorozuya.ObjectUtils;
|
||||
|
||||
public class GalleryHeader extends ViewGroup {
|
||||
|
||||
private final Rect batteryRect = new Rect();
|
||||
private final Rect progressRect = new Rect();
|
||||
private final Rect clockRect = new Rect();
|
||||
private final int[] location = new int[2];
|
||||
private DisplayCutoutCompat displayCutout;
|
||||
private int topInsets = 0;
|
||||
private View battery;
|
||||
private View progress;
|
||||
private View clock;
|
||||
private int lastX = 0;
|
||||
private int lastY = 0;
|
||||
|
||||
public GalleryHeader(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public void setDisplayCutout(@Nullable DisplayCutoutCompat displayCutout) {
|
||||
if (!ObjectUtils.equal(this.displayCutout, displayCutout)) {
|
||||
this.displayCutout = displayCutout;
|
||||
requestLayout();
|
||||
}
|
||||
}
|
||||
|
||||
public void setTopInsets(int topInsets) {
|
||||
if (this.topInsets != topInsets) {
|
||||
this.topInsets = topInsets;
|
||||
requestLayout();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
|
||||
battery = findViewById(R.id.battery);
|
||||
progress = findViewById(R.id.progress);
|
||||
clock = findViewById(R.id.clock);
|
||||
}
|
||||
|
||||
private void measureChild(Rect rect, View view, int width, int paddingLeft, int paddingRight) {
|
||||
int left;
|
||||
MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams();
|
||||
if (view == battery) {
|
||||
left = paddingLeft + lp.leftMargin;
|
||||
} else if (view == progress) {
|
||||
left = paddingLeft + (width - paddingLeft - paddingRight) / 2 - view.getMeasuredWidth() / 2;
|
||||
} else {
|
||||
left = width - paddingRight - lp.rightMargin - view.getMeasuredWidth();
|
||||
}
|
||||
rect.set(left, lp.topMargin + topInsets, left + view.getMeasuredWidth(), lp.topMargin + topInsets + view.getMeasuredHeight());
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.P)
|
||||
private boolean offsetVertically(Rect rect, View view, int width) {
|
||||
int offset = 0;
|
||||
|
||||
measureChild(rect, view, width, 0, 0);
|
||||
rect.offset(lastX, lastY);
|
||||
|
||||
for (Rect notch : displayCutout.getBoundingRects()) {
|
||||
if (Rect.intersects(notch, rect)) {
|
||||
offset = Math.max(offset, notch.bottom - lastY);
|
||||
}
|
||||
}
|
||||
|
||||
if (offset != 0) {
|
||||
rect.offset(-lastX, -lastY);
|
||||
rect.offset(0, offset);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.P)
|
||||
private int getOffsetLeft(Rect rect, View view, int width) {
|
||||
int offset = 0;
|
||||
|
||||
measureChild(rect, view, width, 0, 0);
|
||||
rect.offset(lastX, lastY);
|
||||
|
||||
for (Rect notch : displayCutout.getBoundingRects()) {
|
||||
if (Rect.intersects(notch, rect)) {
|
||||
offset = Math.max(offset, notch.right - lastX);
|
||||
}
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.P)
|
||||
private int getOffsetRight(Rect rect, View view, int width) {
|
||||
int offset = 0;
|
||||
|
||||
measureChild(rect, view, width, 0, 0);
|
||||
rect.offset(lastX, lastY);
|
||||
|
||||
for (Rect notch : displayCutout.getBoundingRects()) {
|
||||
if (Rect.intersects(notch, rect)) {
|
||||
offset = Math.max(offset, lastX + width - notch.left);
|
||||
}
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
int width = MeasureSpec.getSize(widthMeasureSpec);
|
||||
|
||||
int height = 0;
|
||||
for (int i = 0; i < getChildCount(); i++) {
|
||||
View child = getChildAt(i);
|
||||
measureChild(child, widthMeasureSpec, heightMeasureSpec);
|
||||
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
|
||||
height = Math.max(height, child.getMeasuredHeight() + lp.topMargin + topInsets);
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && displayCutout != null) {
|
||||
// Check progress covered
|
||||
if (offsetVertically(progressRect, progress, width)) {
|
||||
offsetVertically(batteryRect, battery, width);
|
||||
offsetVertically(clockRect, clock, width);
|
||||
height = Math.max(progressRect.bottom, Math.max(batteryRect.bottom, clockRect.bottom));
|
||||
} else {
|
||||
// Clamp left and right
|
||||
int paddingLeft = getOffsetLeft(batteryRect, battery, width);
|
||||
int paddingRight = getOffsetRight(clockRect, clock, width);
|
||||
measureChild(batteryRect, battery, width, paddingLeft, paddingRight);
|
||||
measureChild(progressRect, progress, width, paddingLeft, paddingRight);
|
||||
measureChild(clockRect, clock, width, paddingLeft, paddingRight);
|
||||
}
|
||||
} else {
|
||||
measureChild(batteryRect, battery, width, 0, 0);
|
||||
measureChild(progressRect, progress, width, 0, 0);
|
||||
measureChild(clockRect, clock, width, 0, 0);
|
||||
}
|
||||
|
||||
setMeasuredDimension(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
battery.layout(batteryRect.left, batteryRect.top, batteryRect.right, batteryRect.bottom);
|
||||
progress.layout(progressRect.left, progressRect.top, progressRect.right, progressRect.bottom);
|
||||
clock.layout(clockRect.left, clockRect.top, clockRect.right, clockRect.bottom);
|
||||
|
||||
getLocationOnScreen(location);
|
||||
if (lastX != location[0] || lastY != location[1]) {
|
||||
lastX = location[0];
|
||||
lastY = location[1];
|
||||
requestLayout();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MarginLayoutParams generateLayoutParams(AttributeSet attrs) {
|
||||
return new MarginLayoutParams(getContext(), attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
|
||||
return p instanceof MarginLayoutParams;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MarginLayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
|
||||
if (lp instanceof MarginLayoutParams) {
|
||||
return new MarginLayoutParams((MarginLayoutParams) lp);
|
||||
}
|
||||
return new MarginLayoutParams(lp);
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import androidx.appcompat.widget.AppCompatSeekBar;
|
||||
|
||||
public class ReversibleSeekBar extends AppCompatSeekBar {
|
||||
|
||||
private boolean mReverse;
|
||||
|
||||
public ReversibleSeekBar(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public ReversibleSeekBar(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public ReversibleSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public void setReverse(boolean reverse) {
|
||||
mReverse = reverse;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
boolean reverse = mReverse;
|
||||
int saveCount = 0;
|
||||
if (reverse) {
|
||||
saveCount = canvas.save();
|
||||
float px = this.getWidth() / 2.0f;
|
||||
float py = this.getHeight() / 2.0f;
|
||||
canvas.scale(-1, 1, px, py);
|
||||
}
|
||||
super.draw(canvas);
|
||||
if (reverse) {
|
||||
canvas.restoreToCount(saveCount);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
boolean reverse = mReverse;
|
||||
float x = 0.0f, y = 0.0f;
|
||||
if (reverse) {
|
||||
x = event.getX();
|
||||
y = event.getY();
|
||||
event.setLocation(getWidth() - x, y);
|
||||
}
|
||||
boolean result = super.onTouchEvent(event);
|
||||
if (reverse) {
|
||||
event.setLocation(x, y);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.widget;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.SeekBar;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.yorozuya.ViewUtils;
|
||||
|
||||
public class SeekBarPanel extends LinearLayout {
|
||||
|
||||
private final int[] mLocation = new int[2];
|
||||
private SeekBar mSeekBar;
|
||||
|
||||
public SeekBarPanel(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public SeekBarPanel(Context context, @Nullable @org.jetbrains.annotations.Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public SeekBarPanel(Context context, @Nullable @org.jetbrains.annotations.Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init();
|
||||
}
|
||||
|
||||
public SeekBarPanel(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
post(() -> {
|
||||
var rootWindowInsets = getRootWindowInsets();
|
||||
if (rootWindowInsets != null) {
|
||||
setPadding(0, 0, 0, rootWindowInsets.getSystemWindowInsetBottom());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
mSeekBar = (SeekBar) ViewUtils.$$(this, R.id.seek_bar);
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@Override
|
||||
public boolean onTouchEvent(@NonNull MotionEvent event) {
|
||||
if (mSeekBar == null) {
|
||||
return super.onTouchEvent(event);
|
||||
} else {
|
||||
ViewUtils.getLocationInAncestor(mSeekBar, mLocation, this);
|
||||
final float offsetX = -mLocation[0];
|
||||
final float offsetY = -mLocation[1];
|
||||
event.offsetLocation(offsetX, offsetY);
|
||||
mSeekBar.onTouchEvent(event);
|
||||
event.offsetLocation(-offsetX, -offsetY);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glgallery;
|
||||
|
||||
import android.view.MotionEvent;
|
||||
|
||||
class DownUpDetector {
|
||||
private final DownUpListener mListener;
|
||||
private boolean mStillDown;
|
||||
|
||||
public DownUpDetector(DownUpListener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
private void setState(boolean down, MotionEvent e) {
|
||||
if (down == mStillDown) return;
|
||||
mStillDown = down;
|
||||
if (down) {
|
||||
mListener.onDown(e);
|
||||
} else {
|
||||
mListener.onUp(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void onTouchEvent(MotionEvent ev) {
|
||||
switch (ev.getActionMasked()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
setState(true, ev);
|
||||
break;
|
||||
case MotionEvent.ACTION_POINTER_DOWN:
|
||||
mListener.onPointerDown(ev);
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
mListener.onPointerUp(ev);
|
||||
setState(false, ev);
|
||||
break;
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
setState(false, ev);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isDown() {
|
||||
return mStillDown;
|
||||
}
|
||||
|
||||
public interface DownUpListener {
|
||||
void onDown(MotionEvent e);
|
||||
|
||||
void onUp(MotionEvent e);
|
||||
|
||||
void onPointerDown(MotionEvent e);
|
||||
|
||||
void onPointerUp(MotionEvent e);
|
||||
}
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glgallery;
|
||||
|
||||
import android.content.Context;
|
||||
import android.hardware.SensorManager;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import com.hippo.glview.anim.Animation;
|
||||
|
||||
abstract class Fling extends Animation {
|
||||
|
||||
private static final float DECELERATION_RATE = (float) (Math.log(0.78) / Math.log(0.9));
|
||||
private static final float INFLEXION = 0.35f; // Tension lines cross at (INFLEXION, 1)
|
||||
private static final float START_TENSION = 0.5f;
|
||||
private static final float END_TENSION = 1.0f;
|
||||
private static final float P1 = START_TENSION * INFLEXION;
|
||||
private static final float P2 = 1.0f - END_TENSION * (1.0f - INFLEXION);
|
||||
private static final float FLING_FRICTION = ViewConfiguration.getScrollFriction();
|
||||
|
||||
private static final int NB_SAMPLES = 100;
|
||||
private static final float[] SPLINE_POSITION = new float[NB_SAMPLES + 1];
|
||||
private static final Interpolator FLING_INTERPOLATOR = new Interpolator() {
|
||||
@Override
|
||||
public float getInterpolation(float input) {
|
||||
final int index = (int) (NB_SAMPLES * input);
|
||||
float distanceCoef = 1.f;
|
||||
float velocityCoef;
|
||||
if (index < NB_SAMPLES) {
|
||||
final float t_inf = (float) index / NB_SAMPLES;
|
||||
final float t_sup = (float) (index + 1) / NB_SAMPLES;
|
||||
final float d_inf = SPLINE_POSITION[index];
|
||||
final float d_sup = SPLINE_POSITION[index + 1];
|
||||
velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
|
||||
distanceCoef = d_inf + (input - t_inf) * velocityCoef;
|
||||
}
|
||||
return distanceCoef;
|
||||
}
|
||||
};
|
||||
private static final float[] SPLINE_TIME = new float[NB_SAMPLES + 1];
|
||||
|
||||
static {
|
||||
float x_min = 0.0f;
|
||||
float y_min = 0.0f;
|
||||
for (int i = 0; i < NB_SAMPLES; i++) {
|
||||
final float alpha = (float) i / NB_SAMPLES;
|
||||
|
||||
float x_max = 1.0f;
|
||||
float x, tx, coef;
|
||||
while (true) {
|
||||
x = x_min + (x_max - x_min) / 2.0f;
|
||||
coef = 3.0f * x * (1.0f - x);
|
||||
tx = coef * ((1.0f - x) * P1 + x * P2) + x * x * x;
|
||||
if (Math.abs(tx - alpha) < 1E-5) break;
|
||||
if (tx > alpha) x_max = x;
|
||||
else x_min = x;
|
||||
}
|
||||
SPLINE_POSITION[i] = coef * ((1.0f - x) * START_TENSION + x) + x * x * x;
|
||||
|
||||
float y_max = 1.0f;
|
||||
float y, dy;
|
||||
while (true) {
|
||||
y = y_min + (y_max - y_min) / 2.0f;
|
||||
coef = 3.0f * y * (1.0f - y);
|
||||
dy = coef * ((1.0f - y) * START_TENSION + y) + y * y * y;
|
||||
if (Math.abs(dy - alpha) < 1E-5) break;
|
||||
if (dy > alpha) y_max = y;
|
||||
else y_min = y;
|
||||
}
|
||||
SPLINE_TIME[i] = coef * ((1.0f - y) * P1 + y * P2) + y * y * y;
|
||||
}
|
||||
SPLINE_POSITION[NB_SAMPLES] = SPLINE_TIME[NB_SAMPLES] = 1.0f;
|
||||
}
|
||||
|
||||
private final float mPhysicalCoeff;
|
||||
|
||||
public Fling(Context context) {
|
||||
final float ppi = context.getResources().getDisplayMetrics().density * 160.0f;
|
||||
mPhysicalCoeff = SensorManager.GRAVITY_EARTH // g (m/s^2)
|
||||
* 39.37f // inch/meter
|
||||
* ppi
|
||||
* 0.84f; // look and feel tuning
|
||||
setInterpolator(FLING_INTERPOLATOR);
|
||||
}
|
||||
|
||||
private double getSplineDeceleration(int velocity) {
|
||||
return Math.log(INFLEXION * Math.abs(velocity) / (FLING_FRICTION * mPhysicalCoeff));
|
||||
}
|
||||
|
||||
/* Returns the duration, expressed in milliseconds */
|
||||
protected int getSplineFlingDuration(int velocity) {
|
||||
final double l = getSplineDeceleration(velocity);
|
||||
final double decelMinusOne = DECELERATION_RATE - 1.0;
|
||||
return (int) (1000.0 * Math.exp(l / decelMinusOne));
|
||||
}
|
||||
|
||||
protected double getSplineFlingDistance(int velocity) {
|
||||
final double l = getSplineDeceleration(velocity);
|
||||
final double decelMinusOne = DECELERATION_RATE - 1.0;
|
||||
return FLING_FRICTION * mPhysicalCoeff * Math.exp(DECELERATION_RATE / decelMinusOne * l);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies mDuration to the duration it takes to get from start to newFinal using the
|
||||
* spline interpolation. The previous duration was needed to get to oldFinal.
|
||||
**/
|
||||
protected int adjustDuration(int start, int oldFinal, int newFinal, int duration) {
|
||||
final int oldDistance = oldFinal - start;
|
||||
final int newDistance = newFinal - start;
|
||||
final float x = Math.abs((float) newDistance / oldDistance);
|
||||
final int index = (int) (NB_SAMPLES * x);
|
||||
if (index < NB_SAMPLES) {
|
||||
final float x_inf = (float) index / NB_SAMPLES;
|
||||
final float x_sup = (float) (index + 1) / NB_SAMPLES;
|
||||
final float t_inf = SPLINE_TIME[index];
|
||||
final float t_sup = SPLINE_TIME[index + 1];
|
||||
final float timeCoef = t_inf + (x - x_inf) / (x_sup - x_inf) * (t_sup - t_inf);
|
||||
duration *= timeCoef;
|
||||
}
|
||||
return duration;
|
||||
}
|
||||
}
|
||||
@@ -1,197 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glgallery;
|
||||
|
||||
import com.hippo.glview.glrenderer.BasicTexture;
|
||||
import com.hippo.glview.glrenderer.Texture;
|
||||
import com.hippo.glview.image.GLImageMovableTextView;
|
||||
import com.hippo.glview.image.ImageMovableTextTexture;
|
||||
import com.hippo.glview.image.ImageTexture;
|
||||
import com.hippo.glview.view.Gravity;
|
||||
import com.hippo.glview.widget.GLFrameLayout;
|
||||
import com.hippo.glview.widget.GLLinearLayout;
|
||||
import com.hippo.glview.widget.GLProgressView;
|
||||
import com.hippo.glview.widget.GLTextureView;
|
||||
|
||||
public class GalleryPageView extends GLFrameLayout {
|
||||
|
||||
public static final int INVALID_INDEX = -1;
|
||||
|
||||
public static final float PROGRESS_GONE = -1.0f;
|
||||
public static final float PROGRESS_INDETERMINATE = -2.0f;
|
||||
|
||||
private final ImageView mImage;
|
||||
private final GLLinearLayout mInfo;
|
||||
private final GLImageMovableTextView mPage;
|
||||
private final GLTextureView mError;
|
||||
private final GLProgressView mProgress;
|
||||
|
||||
private final int mMinHeight;
|
||||
|
||||
private int mIndex = INVALID_INDEX;
|
||||
|
||||
public GalleryPageView(ImageMovableTextTexture pageTextTexture,
|
||||
int progressColor, int progressBgColor, int progressSize,
|
||||
int minHeight, int infoInterval) {
|
||||
// Add image
|
||||
mImage = new ImageView();
|
||||
GravityLayoutParams glp = new GravityLayoutParams(LayoutParams.MATCH_PARENT,
|
||||
LayoutParams.MATCH_PARENT);
|
||||
addComponent(mImage, glp);
|
||||
|
||||
// Add other panel
|
||||
mInfo = new GLLinearLayout();
|
||||
mInfo.setOrientation(GLLinearLayout.VERTICAL);
|
||||
mInfo.setInterval(infoInterval);
|
||||
glp = new GravityLayoutParams(LayoutParams.WRAP_CONTENT,
|
||||
LayoutParams.WRAP_CONTENT);
|
||||
glp.gravity = Gravity.CENTER;
|
||||
addComponent(mInfo, glp);
|
||||
|
||||
// Add page
|
||||
mPage = new GLImageMovableTextView();
|
||||
mPage.setTextTexture(pageTextTexture);
|
||||
GLLinearLayout.LayoutParams lp = new GLLinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
|
||||
LayoutParams.WRAP_CONTENT);
|
||||
lp.gravity = Gravity.CENTER_HORIZONTAL;
|
||||
mInfo.addComponent(mPage, lp);
|
||||
|
||||
// Add error
|
||||
mError = new GLTextureView();
|
||||
lp = new GLLinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
|
||||
LayoutParams.WRAP_CONTENT);
|
||||
lp.gravity = Gravity.CENTER_HORIZONTAL;
|
||||
mInfo.addComponent(mError, lp);
|
||||
|
||||
// Add progress
|
||||
mProgress = new GLProgressView();
|
||||
mProgress.setBgColor(progressBgColor);
|
||||
mProgress.setColor(progressColor);
|
||||
mProgress.setMinimumWidth(progressSize);
|
||||
mProgress.setMinimumHeight(progressSize);
|
||||
lp = new GLLinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
|
||||
LayoutParams.WRAP_CONTENT);
|
||||
lp.gravity = Gravity.CENTER_HORIZONTAL;
|
||||
mInfo.addComponent(mProgress, lp);
|
||||
|
||||
mMinHeight = minHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getSuggestedMinimumHeight() {
|
||||
// The height of the actual image may be smaller than mPageMinHeight.
|
||||
// Set min height as 0 when the image is visible.
|
||||
// For PageLayoutManager, min height is useless.
|
||||
if (mImage.getVisibility() == VISIBLE) {
|
||||
return 0;
|
||||
} else {
|
||||
return mMinHeight;
|
||||
}
|
||||
}
|
||||
|
||||
int getIndex() {
|
||||
return mIndex;
|
||||
}
|
||||
|
||||
void setIndex(int index) {
|
||||
mIndex = index;
|
||||
}
|
||||
|
||||
public void showImage() {
|
||||
mImage.setVisibility(VISIBLE);
|
||||
mInfo.setVisibility(GONE);
|
||||
}
|
||||
|
||||
public void showInfo() {
|
||||
// For image valid rect
|
||||
mImage.setVisibility(INVISIBLE);
|
||||
mInfo.setVisibility(VISIBLE);
|
||||
}
|
||||
|
||||
private void unbindImage() {
|
||||
ImageTexture texture = mImage.getImageTexture();
|
||||
if (texture != null) {
|
||||
mImage.setImageTexture(null);
|
||||
texture.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
public void setImage(ImageTexture imageTexture) {
|
||||
unbindImage();
|
||||
if (imageTexture != null) {
|
||||
mImage.setImageTexture(imageTexture);
|
||||
}
|
||||
}
|
||||
|
||||
public void setPage(int page) {
|
||||
mPage.setVisibility(VISIBLE);
|
||||
mPage.setText(Integer.toString(page));
|
||||
}
|
||||
|
||||
public void hidePage() {
|
||||
mPage.setVisibility(GONE);
|
||||
}
|
||||
|
||||
public void setProgress(float progress) {
|
||||
if (progress == PROGRESS_GONE) {
|
||||
mProgress.setVisibility(GONE);
|
||||
} else if (progress == PROGRESS_INDETERMINATE) {
|
||||
mProgress.setVisibility(VISIBLE);
|
||||
mProgress.setIndeterminate(true);
|
||||
} else {
|
||||
mProgress.setVisibility(VISIBLE);
|
||||
mProgress.setIndeterminate(false);
|
||||
mProgress.setProgress(progress);
|
||||
}
|
||||
}
|
||||
|
||||
private void unbindError() {
|
||||
Texture texture = mError.getTexture();
|
||||
if (texture != null) {
|
||||
mError.setTexture(null);
|
||||
if (texture instanceof BasicTexture) {
|
||||
((BasicTexture) texture).recycle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setError(String error, GalleryView galleryView) {
|
||||
unbindError();
|
||||
if (error == null) {
|
||||
mError.setVisibility(GONE);
|
||||
} else {
|
||||
mError.setVisibility(VISIBLE);
|
||||
galleryView.bindErrorView(mError, error);
|
||||
}
|
||||
}
|
||||
|
||||
ImageView getImageView() {
|
||||
return mImage;
|
||||
}
|
||||
|
||||
boolean isLoaded() {
|
||||
return mImage.getVisibility() == VISIBLE;
|
||||
}
|
||||
|
||||
boolean isError() {
|
||||
return mError.getVisibility() == VISIBLE;
|
||||
}
|
||||
|
||||
boolean isUnderInfo(float x, float y) {
|
||||
return mInfo.bounds().contains((int) x, (int) y);
|
||||
}
|
||||
}
|
||||
@@ -1,263 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glgallery;
|
||||
|
||||
import android.util.LruCache;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
import com.hippo.glview.glrenderer.GLCanvas;
|
||||
import com.hippo.glview.image.ImageWrapper;
|
||||
import com.hippo.glview.view.GLRoot;
|
||||
import com.hippo.image.Image;
|
||||
import com.hippo.yorozuya.ConcurrentPool;
|
||||
import com.hippo.yorozuya.OSUtils;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
public abstract class GalleryProvider {
|
||||
|
||||
public static final int STATE_WAIT = -1;
|
||||
public static final int STATE_ERROR = -2;
|
||||
|
||||
private final ConcurrentPool<NotifyTask> mNotifyTaskPool = new ConcurrentPool<>(5);
|
||||
private final ImageCache mImageCache = new ImageCache();
|
||||
private volatile Listener mListener;
|
||||
private volatile GLRoot mGLRoot;
|
||||
private boolean mStarted = false;
|
||||
|
||||
@UiThread
|
||||
public void start() {
|
||||
OSUtils.checkMainLoop();
|
||||
|
||||
if (mStarted) {
|
||||
throw new IllegalStateException("Can't start it twice");
|
||||
}
|
||||
mStarted = true;
|
||||
}
|
||||
|
||||
@UiThread
|
||||
public void stop() {
|
||||
OSUtils.checkMainLoop();
|
||||
mImageCache.evictAll();
|
||||
}
|
||||
|
||||
public void setGLRoot(GLRoot glRoot) {
|
||||
mGLRoot = glRoot;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@link #STATE_WAIT} for wait,
|
||||
* {@link #STATE_ERROR} for error, {@link #getError()} to get error message,
|
||||
* 0 for empty
|
||||
*/
|
||||
public abstract int size();
|
||||
|
||||
public final void request(int index) {
|
||||
ImageWrapper imageWrapper = mImageCache.get(index);
|
||||
if (imageWrapper != null) {
|
||||
notifyPageSucceed(index, imageWrapper);
|
||||
} else {
|
||||
onRequest(index);
|
||||
}
|
||||
}
|
||||
|
||||
public final void forceRequest(int index) {
|
||||
onForceRequest(index);
|
||||
}
|
||||
|
||||
public void removeCache(int index) {
|
||||
mImageCache.remove(index);
|
||||
}
|
||||
|
||||
protected abstract void onRequest(int index);
|
||||
|
||||
protected abstract void onForceRequest(int index);
|
||||
|
||||
public final void cancelRequest(int index) {
|
||||
onCancelRequest(index);
|
||||
}
|
||||
|
||||
protected abstract void onCancelRequest(int index);
|
||||
|
||||
public abstract String getError();
|
||||
|
||||
public void setListener(Listener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
public void notifyDataChanged() {
|
||||
notify(NotifyTask.TYPE_DATA_CHANGED, -1, 0.0f, null, null);
|
||||
}
|
||||
|
||||
public void notifyDataChanged(int index) {
|
||||
notify(NotifyTask.TYPE_DATA_CHANGED, index, 0.0f, null, null);
|
||||
}
|
||||
|
||||
public void notifyPageWait(int index) {
|
||||
notify(NotifyTask.TYPE_WAIT, index, 0.0f, null, null);
|
||||
}
|
||||
|
||||
public void notifyPagePercent(int index, float percent) {
|
||||
notify(NotifyTask.TYPE_PERCENT, index, percent, null, null);
|
||||
}
|
||||
|
||||
public void notifyPageSucceed(int index, Image image) {
|
||||
ImageWrapper imageWrapper = new ImageWrapper(image);
|
||||
mImageCache.add(index, imageWrapper);
|
||||
notifyPageSucceed(index, imageWrapper);
|
||||
}
|
||||
|
||||
public void notifyPageSucceed(int index, ImageWrapper image) {
|
||||
notify(NotifyTask.TYPE_SUCCEED, index, 0.0f, image, null);
|
||||
}
|
||||
|
||||
public void notifyPageFailed(int index, String error) {
|
||||
notify(NotifyTask.TYPE_FAILED, index, 0.0f, null, error);
|
||||
}
|
||||
|
||||
private void notify(@NotifyTask.Type int type, int index, float percent, ImageWrapper image, String error) {
|
||||
Listener listener = mListener;
|
||||
if (listener == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
GLRoot glRoot = mGLRoot;
|
||||
if (glRoot == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
NotifyTask task = mNotifyTaskPool.pop();
|
||||
if (task == null) {
|
||||
task = new NotifyTask(listener, mNotifyTaskPool);
|
||||
}
|
||||
task.setData(type, index, percent, image, error);
|
||||
glRoot.addOnGLIdleListener(task);
|
||||
}
|
||||
|
||||
public interface Listener {
|
||||
|
||||
void onDataChanged();
|
||||
|
||||
void onPageWait(int index);
|
||||
|
||||
void onPagePercent(int index, float percent);
|
||||
|
||||
void onPageSucceed(int index, ImageWrapper image);
|
||||
|
||||
void onPageFailed(int index, String error);
|
||||
|
||||
void onDataChanged(int index);
|
||||
}
|
||||
|
||||
private static class NotifyTask implements GLRoot.OnGLIdleListener {
|
||||
|
||||
public static final int TYPE_DATA_CHANGED = 0;
|
||||
public static final int TYPE_WAIT = 1;
|
||||
public static final int TYPE_PERCENT = 2;
|
||||
public static final int TYPE_SUCCEED = 3;
|
||||
public static final int TYPE_FAILED = 4;
|
||||
private final Listener mListener;
|
||||
private final ConcurrentPool<NotifyTask> mPool;
|
||||
@Type
|
||||
private int mType;
|
||||
private int mIndex;
|
||||
private float mPercent;
|
||||
private ImageWrapper mImage;
|
||||
private String mError;
|
||||
|
||||
public NotifyTask(Listener listener, ConcurrentPool<NotifyTask> pool) {
|
||||
mListener = listener;
|
||||
mPool = pool;
|
||||
}
|
||||
|
||||
public void setData(@Type int type, int index, float percent, ImageWrapper image, String error) {
|
||||
mType = type;
|
||||
mIndex = index;
|
||||
mPercent = percent;
|
||||
mImage = image;
|
||||
mError = error;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) {
|
||||
switch (mType) {
|
||||
case TYPE_DATA_CHANGED:
|
||||
if (mIndex < 0) {
|
||||
mListener.onDataChanged();
|
||||
} else {
|
||||
mListener.onDataChanged(mIndex);
|
||||
}
|
||||
break;
|
||||
case TYPE_WAIT:
|
||||
mListener.onPageWait(mIndex);
|
||||
break;
|
||||
case TYPE_PERCENT:
|
||||
mListener.onPagePercent(mIndex, mPercent);
|
||||
break;
|
||||
case TYPE_SUCCEED:
|
||||
mListener.onPageSucceed(mIndex, mImage);
|
||||
break;
|
||||
case TYPE_FAILED:
|
||||
mListener.onPageFailed(mIndex, mError);
|
||||
break;
|
||||
}
|
||||
|
||||
// Clean data
|
||||
mImage = null;
|
||||
mError = null;
|
||||
// Push back
|
||||
mPool.push(this);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@IntDef({TYPE_DATA_CHANGED, NotifyTask.TYPE_WAIT, TYPE_PERCENT, TYPE_SUCCEED, TYPE_FAILED})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface Type {
|
||||
}
|
||||
}
|
||||
|
||||
private static class ImageCache extends LruCache<Integer, ImageWrapper> {
|
||||
|
||||
private static final int CACHE_SIZE = 512 * 1024 * 1024;
|
||||
|
||||
public ImageCache() {
|
||||
super(CACHE_SIZE);
|
||||
}
|
||||
|
||||
public void add(Integer key, ImageWrapper value) {
|
||||
if (!value.getAnimated())
|
||||
if (value.obtain())
|
||||
put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int sizeOf(Integer key, ImageWrapper value) {
|
||||
return value.getWidth() * value.getHeight() * 4;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void entryRemoved(boolean evicted, Integer key, ImageWrapper oldValue, ImageWrapper newValue) {
|
||||
if (oldValue != null) {
|
||||
oldValue.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,186 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glgallery;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.SystemClock;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ScaleGestureDetector;
|
||||
|
||||
import androidx.core.view.GestureDetectorCompat;
|
||||
|
||||
// This class aggregates three gesture detectors: GestureDetector,
|
||||
// ScaleGestureDetector, and DownUpDetector.
|
||||
class GestureRecognizer {
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = "GestureRecognizer";
|
||||
private final GestureDetectorCompat mGestureDetector;
|
||||
private final ScaleGestureDetector mScaleDetector;
|
||||
private final DownUpDetector mDownUpDetector;
|
||||
private final Listener mListener;
|
||||
|
||||
public GestureRecognizer(Context context, Listener listener) {
|
||||
mListener = listener;
|
||||
MyGestureListener gestureListener = new MyGestureListener();
|
||||
mGestureDetector = new GestureDetectorCompat(context, gestureListener,
|
||||
null /* ignoreMultitouch */);
|
||||
mGestureDetector.setOnDoubleTapListener(gestureListener);
|
||||
mScaleDetector = new ScaleGestureDetector(
|
||||
context, new MyScaleListener());
|
||||
mDownUpDetector = new DownUpDetector(new MyDownUpListener());
|
||||
}
|
||||
|
||||
public void onTouchEvent(MotionEvent event) {
|
||||
mScaleDetector.onTouchEvent(event);
|
||||
mGestureDetector.onTouchEvent(event);
|
||||
mDownUpDetector.onTouchEvent(event);
|
||||
}
|
||||
|
||||
public boolean isDown() {
|
||||
return mDownUpDetector.isDown();
|
||||
}
|
||||
|
||||
public void cancelScale() {
|
||||
long now = SystemClock.uptimeMillis();
|
||||
MotionEvent cancelEvent = MotionEvent.obtain(
|
||||
now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0);
|
||||
mScaleDetector.onTouchEvent(cancelEvent);
|
||||
cancelEvent.recycle();
|
||||
}
|
||||
|
||||
public interface Listener {
|
||||
boolean onSingleTapUp(float x, float y);
|
||||
|
||||
boolean onSingleTapConfirmed(float x, float y);
|
||||
|
||||
boolean onDoubleTap(float x, float y);
|
||||
|
||||
boolean onDoubleTapConfirmed(float x, float y);
|
||||
|
||||
void onLongPress(float x, float y);
|
||||
|
||||
boolean onScroll(float dx, float dy, float totalX, float totalY, float x, float y);
|
||||
|
||||
/**
|
||||
* @param velocityX Finger from top to bottom is positive
|
||||
* @param velocityY Finger from left to right is positive
|
||||
*/
|
||||
boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
|
||||
|
||||
boolean onScaleBegin(float focusX, float focusY);
|
||||
|
||||
boolean onScale(float focusX, float focusY, float scale);
|
||||
|
||||
void onScaleEnd();
|
||||
|
||||
void onDown(float x, float y);
|
||||
|
||||
void onUp();
|
||||
|
||||
void onPointerDown(float x, float y);
|
||||
|
||||
void onPointerUp();
|
||||
}
|
||||
|
||||
private class MyGestureListener
|
||||
extends GestureDetector.SimpleOnGestureListener {
|
||||
@Override
|
||||
public boolean onSingleTapUp(MotionEvent e) {
|
||||
return mListener.onSingleTapUp(e.getX(), e.getY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSingleTapConfirmed(MotionEvent e) {
|
||||
return mListener.onSingleTapConfirmed(e.getX(), e.getY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDoubleTapEvent(MotionEvent e) {
|
||||
if (e.getAction() == MotionEvent.ACTION_UP) {
|
||||
return mListener.onDoubleTapConfirmed(e.getX(), e.getY());
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDoubleTap(MotionEvent e) {
|
||||
return mListener.onDoubleTap(e.getX(), e.getY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLongPress(MotionEvent e) {
|
||||
mListener.onLongPress(e.getX(), e.getY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScroll(
|
||||
MotionEvent e1, MotionEvent e2, float dx, float dy) {
|
||||
return mListener.onScroll(
|
||||
dx, dy, e2.getX() - e1.getX(), e2.getY() - e1.getY(), e2.getX(), e2.getY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
|
||||
float velocityY) {
|
||||
return mListener.onFling(e1, e2, velocityX, velocityY);
|
||||
}
|
||||
}
|
||||
|
||||
private class MyScaleListener
|
||||
extends ScaleGestureDetector.SimpleOnScaleGestureListener {
|
||||
@Override
|
||||
public boolean onScaleBegin(ScaleGestureDetector detector) {
|
||||
return mListener.onScaleBegin(
|
||||
detector.getFocusX(), detector.getFocusY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScale(ScaleGestureDetector detector) {
|
||||
return mListener.onScale(detector.getFocusX(),
|
||||
detector.getFocusY(), detector.getScaleFactor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScaleEnd(ScaleGestureDetector detector) {
|
||||
mListener.onScaleEnd();
|
||||
}
|
||||
}
|
||||
|
||||
private class MyDownUpListener implements DownUpDetector.DownUpListener {
|
||||
@Override
|
||||
public void onDown(MotionEvent e) {
|
||||
mListener.onDown(e.getX(), e.getY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUp(MotionEvent e) {
|
||||
mListener.onUp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPointerDown(MotionEvent e) {
|
||||
mListener.onPointerDown(e.getX(), e.getY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPointerUp(MotionEvent e) {
|
||||
mListener.onPointerUp();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,597 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glgallery;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
|
||||
import com.hippo.glview.anim.AlphaAnimation;
|
||||
import com.hippo.glview.glrenderer.GLCanvas;
|
||||
import com.hippo.glview.glrenderer.Texture;
|
||||
import com.hippo.glview.image.ImageTexture;
|
||||
import com.hippo.glview.view.GLView;
|
||||
import com.hippo.yorozuya.AnimationUtils;
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
class ImageView extends GLView implements ImageTexture.Callback {
|
||||
|
||||
public static final int SCALE_ORIGIN = 0;
|
||||
public static final int SCALE_FIT_WIDTH = 1;
|
||||
public static final int SCALE_FIT_HEIGHT = 2;
|
||||
public static final int SCALE_FIT = 3;
|
||||
public static final int SCALE_FIXED = 4;
|
||||
public static final int START_POSITION_TOP_LEFT = 0;
|
||||
public static final int START_POSITION_TOP_RIGHT = 1;
|
||||
public static final int START_POSITION_BOTTOM_LEFT = 2;
|
||||
public static final int START_POSITION_BOTTOM_RIGHT = 3;
|
||||
public static final int START_POSITION_CENTER = 4;
|
||||
// TODO adjust scale max and min according to image size and screen size
|
||||
private static final float SCALE_MIN = 1 / 10.0f;
|
||||
private static final float SCALE_MAX = 10.0f;
|
||||
private static final long ALPHA_ANIMATION_DURING = 300L;
|
||||
private final RectF mDst = new RectF();
|
||||
private final RectF mSrcActual = new RectF();
|
||||
private final RectF mDstActual = new RectF();
|
||||
private final Rect mValidRect = new Rect();
|
||||
private final AlphaAnimation mAlphaAnimation;
|
||||
private ImageTexture mImageTexture;
|
||||
private int mTextureWidth;
|
||||
private int mTextureHeight;
|
||||
private int mScaleMode = SCALE_FIT;
|
||||
private int mStartPosition = START_POSITION_TOP_RIGHT;
|
||||
private float mScaleValue = 1.0f;
|
||||
private float mScale = 1.0f;
|
||||
private boolean mScaleOffsetDirty = true;
|
||||
private boolean mPositionInRootDirty = true;
|
||||
|
||||
public ImageView() {
|
||||
mAlphaAnimation = new AlphaAnimation(0.0f, 1.0f);
|
||||
mAlphaAnimation.setDuration(ALPHA_ANIMATION_DURING);
|
||||
mAlphaAnimation.setInterpolator(AnimationUtils.FAST_SLOW_INTERPOLATOR);
|
||||
}
|
||||
|
||||
public static int sanitizeScaleMode(int scaleMode) {
|
||||
if (scaleMode != SCALE_ORIGIN &&
|
||||
scaleMode != SCALE_FIT_WIDTH &&
|
||||
scaleMode != SCALE_FIT_HEIGHT &&
|
||||
scaleMode != SCALE_FIT &&
|
||||
scaleMode != SCALE_FIXED) {
|
||||
return SCALE_FIT;
|
||||
} else {
|
||||
return scaleMode;
|
||||
}
|
||||
}
|
||||
|
||||
public static int sanitizeStartPosition(int startPosition) {
|
||||
if (startPosition != START_POSITION_TOP_LEFT &&
|
||||
startPosition != START_POSITION_TOP_RIGHT &&
|
||||
startPosition != START_POSITION_BOTTOM_LEFT &&
|
||||
startPosition != START_POSITION_BOTTOM_RIGHT &&
|
||||
startPosition != START_POSITION_CENTER) {
|
||||
return START_POSITION_TOP_RIGHT;
|
||||
} else {
|
||||
return startPosition;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getSuggestedMinimumWidth() {
|
||||
return Math.max(super.getSuggestedMinimumWidth(),
|
||||
mImageTexture == null ? 0 : mTextureWidth);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getSuggestedMinimumHeight() {
|
||||
return Math.max(super.getSuggestedMinimumHeight(),
|
||||
mImageTexture == null ? 0 : mTextureHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthSpec, int heightSpec) {
|
||||
if (mImageTexture == null) {
|
||||
super.onMeasure(widthSpec, heightSpec);
|
||||
} else {
|
||||
float ratio = (float) mTextureWidth / mTextureHeight;
|
||||
int widthSize = MeasureSpec.getSize(widthSpec);
|
||||
int heightSize = MeasureSpec.getSize(heightSpec);
|
||||
int widthMode = MeasureSpec.getMode(widthSpec);
|
||||
int heightMode = MeasureSpec.getMode(heightSpec);
|
||||
int measureWidth = -1;
|
||||
int measureHeight = -1;
|
||||
|
||||
if (widthMode == MeasureSpec.EXACTLY) {
|
||||
measureWidth = widthSize;
|
||||
if (heightMode == MeasureSpec.EXACTLY) {
|
||||
measureHeight = heightSize;
|
||||
} else {
|
||||
measureHeight = (int) (widthSize / ratio);
|
||||
if (heightMode == MeasureSpec.AT_MOST) {
|
||||
measureHeight = Math.min(measureHeight, heightSize);
|
||||
}
|
||||
}
|
||||
} else if (heightMode == MeasureSpec.EXACTLY) {
|
||||
measureHeight = heightSize;
|
||||
measureWidth = (int) (heightSize * ratio);
|
||||
if (widthMode == MeasureSpec.AT_MOST) {
|
||||
measureWidth = Math.min(measureWidth, widthSize);
|
||||
}
|
||||
}
|
||||
|
||||
if (measureWidth == -1 || measureHeight == -1) {
|
||||
super.onMeasure(widthSpec, heightSpec);
|
||||
} else {
|
||||
setMeasuredSize(measureWidth, measureHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int newW, int newH, int oldW, int oldH) {
|
||||
mScaleOffsetDirty = true;
|
||||
mPositionInRootDirty = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPositionInRootChanged(int x, int y, int oldX, int oldY) {
|
||||
mPositionInRootDirty = true;
|
||||
|
||||
if (mImageTexture != null) {
|
||||
getValidRect(mValidRect);
|
||||
if (!mValidRect.isEmpty()) {
|
||||
mImageTexture.start();
|
||||
} else {
|
||||
mImageTexture.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void getScaleDefault(float[] scaleDefault) {
|
||||
if (mImageTexture == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
scaleDefault[0] = 1.0f;
|
||||
scaleDefault[1] = (float) getWidth() / mTextureWidth;
|
||||
scaleDefault[2] = (float) getHeight() / mTextureHeight;
|
||||
scaleDefault[3] = Math.max(scaleDefault[1], scaleDefault[2]) * 2;
|
||||
|
||||
scaleDefault[0] = MathUtils.clamp(scaleDefault[0], SCALE_MIN, SCALE_MAX);
|
||||
scaleDefault[1] = MathUtils.clamp(scaleDefault[1], SCALE_MIN, SCALE_MAX);
|
||||
scaleDefault[2] = MathUtils.clamp(scaleDefault[2], SCALE_MIN, SCALE_MAX);
|
||||
scaleDefault[3] = MathUtils.clamp(scaleDefault[3], SCALE_MIN, SCALE_MAX);
|
||||
|
||||
Arrays.sort(scaleDefault);
|
||||
}
|
||||
|
||||
public ImageTexture getImageTexture() {
|
||||
return mImageTexture;
|
||||
}
|
||||
|
||||
public void setImageTexture(ImageTexture imageTexture) {
|
||||
// Remove callback
|
||||
if (mImageTexture != null) {
|
||||
mImageTexture.setCallback(null);
|
||||
mImageTexture.stop();
|
||||
}
|
||||
|
||||
int oldTextureWidth = mTextureWidth;
|
||||
int oldTextureHeight = mTextureHeight;
|
||||
|
||||
mImageTexture = imageTexture;
|
||||
|
||||
if (imageTexture != null) {
|
||||
imageTexture.setCallback(this);
|
||||
mTextureWidth = imageTexture.getWidth();
|
||||
mTextureHeight = imageTexture.getHeight();
|
||||
// Avoid zero and negative
|
||||
if (mTextureWidth <= 0) {
|
||||
mTextureWidth = 1;
|
||||
}
|
||||
if (mTextureHeight <= 0) {
|
||||
mTextureHeight = 1;
|
||||
}
|
||||
|
||||
// Start alpha animation, do not show animation for image has no valid rect
|
||||
getValidRect(mValidRect);
|
||||
if (!mValidRect.isEmpty()) {
|
||||
startAnimation(mAlphaAnimation, true);
|
||||
mImageTexture.start();
|
||||
}
|
||||
} else {
|
||||
mTextureWidth = 1;
|
||||
mTextureHeight = 1;
|
||||
}
|
||||
|
||||
mScaleOffsetDirty = true;
|
||||
mPositionInRootDirty = true;
|
||||
|
||||
if (oldTextureWidth != mTextureWidth || oldTextureHeight != mTextureHeight) {
|
||||
requestLayout();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isLoaded() {
|
||||
return mImageTexture != null;
|
||||
}
|
||||
|
||||
public boolean canFlingVertically() {
|
||||
if (mScaleOffsetDirty) {
|
||||
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
|
||||
if (mScaleOffsetDirty) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return mDst.top < 0.0f || mDst.bottom > getHeight();
|
||||
}
|
||||
|
||||
public boolean canFlingHorizontally() {
|
||||
if (mScaleOffsetDirty) {
|
||||
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
|
||||
if (mScaleOffsetDirty) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return mDst.left < 0.0f || mDst.right > getWidth();
|
||||
}
|
||||
|
||||
public boolean canFling() {
|
||||
if (mScaleOffsetDirty) {
|
||||
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
|
||||
if (mScaleOffsetDirty) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return mDst.left < 0.0f || mDst.top < 0.0f || mDst.right > getWidth() || mDst.bottom > getHeight();
|
||||
}
|
||||
|
||||
public int getMaxDx() {
|
||||
if (mScaleOffsetDirty) {
|
||||
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
|
||||
if (mScaleOffsetDirty) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return Math.max(0, -(int) mDst.left);
|
||||
}
|
||||
|
||||
public int getMinDx() {
|
||||
if (mScaleOffsetDirty) {
|
||||
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
|
||||
if (mScaleOffsetDirty) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return Math.min(0, getWidth() - (int) mDst.right);
|
||||
}
|
||||
|
||||
public int getMaxDy() {
|
||||
if (mScaleOffsetDirty) {
|
||||
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
|
||||
if (mScaleOffsetDirty) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return Math.max(0, -(int) mDst.top);
|
||||
}
|
||||
|
||||
public int getMinDy() {
|
||||
if (mScaleOffsetDirty) {
|
||||
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
|
||||
if (mScaleOffsetDirty) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return Math.min(0, getHeight() - (int) mDst.bottom);
|
||||
}
|
||||
|
||||
public float getScale() {
|
||||
return mScale;
|
||||
}
|
||||
|
||||
/**
|
||||
* If target is shorter then screen, make it in screen center. If target is
|
||||
* longer then parent, make sure target fill parent over
|
||||
*/
|
||||
private void adjustPosition() {
|
||||
RectF dst = mDst;
|
||||
int screenWidth = getWidth();
|
||||
int screenHeight = getHeight();
|
||||
float targetWidth = dst.width();
|
||||
float targetHeight = dst.height();
|
||||
|
||||
if (targetWidth > screenWidth) {
|
||||
float fixXOffset = dst.left;
|
||||
if (fixXOffset > 0) {
|
||||
dst.left -= fixXOffset;
|
||||
dst.right -= fixXOffset;
|
||||
} else if ((fixXOffset = screenWidth - dst.right) > 0) {
|
||||
dst.left += fixXOffset;
|
||||
dst.right += fixXOffset;
|
||||
}
|
||||
} else {
|
||||
float left = (screenWidth - targetWidth) / 2;
|
||||
dst.offsetTo(left, dst.top);
|
||||
}
|
||||
if (targetHeight > screenHeight) {
|
||||
float fixYOffset = dst.top;
|
||||
if (fixYOffset > 0) {
|
||||
dst.top -= fixYOffset;
|
||||
dst.bottom -= fixYOffset;
|
||||
} else if ((fixYOffset = screenHeight - dst.bottom) > 0) {
|
||||
dst.top += fixYOffset;
|
||||
dst.bottom += fixYOffset;
|
||||
}
|
||||
} else {
|
||||
float top = (screenHeight - targetHeight) / 2;
|
||||
dst.offsetTo(dst.left, top);
|
||||
}
|
||||
}
|
||||
|
||||
public void setScaleOffset(int scaleMode, int startPosition, float scaleValue) {
|
||||
mScaleMode = scaleMode;
|
||||
mStartPosition = startPosition;
|
||||
mScaleValue = scaleValue;
|
||||
|
||||
int screenWidth = getWidth();
|
||||
int screenHeight = getHeight();
|
||||
|
||||
if (mImageTexture == null || screenWidth == 0 || screenHeight == 0) {
|
||||
mScaleOffsetDirty = true;
|
||||
return;
|
||||
}
|
||||
|
||||
int textureWidth = mTextureWidth;
|
||||
int textureHeight = mTextureHeight;
|
||||
|
||||
// Set scale
|
||||
float targetWidth;
|
||||
float targetHeight;
|
||||
switch (scaleMode) {
|
||||
case SCALE_ORIGIN:
|
||||
mScale = 1.0f;
|
||||
targetWidth = textureWidth;
|
||||
targetHeight = textureHeight;
|
||||
break;
|
||||
case SCALE_FIT_WIDTH:
|
||||
mScale = (float) screenWidth / textureWidth;
|
||||
targetWidth = screenWidth;
|
||||
targetHeight = textureHeight * mScale;
|
||||
break;
|
||||
case SCALE_FIT_HEIGHT:
|
||||
mScale = (float) screenHeight / textureHeight;
|
||||
targetWidth = textureWidth * mScale;
|
||||
targetHeight = screenHeight;
|
||||
break;
|
||||
case SCALE_FIT:
|
||||
float scaleX = (float) screenWidth / textureWidth;
|
||||
float scaleY = (float) screenHeight / textureHeight;
|
||||
if (scaleX < scaleY) {
|
||||
mScale = scaleX;
|
||||
targetWidth = screenWidth;
|
||||
targetHeight = textureHeight * scaleX;
|
||||
} else {
|
||||
mScale = scaleY;
|
||||
targetWidth = textureWidth * scaleY;
|
||||
targetHeight = screenHeight;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case SCALE_FIXED:
|
||||
default:
|
||||
mScale = scaleValue;
|
||||
targetWidth = textureWidth * scaleValue;
|
||||
targetHeight = textureHeight * scaleValue;
|
||||
break;
|
||||
}
|
||||
|
||||
// adjust scale, not too big, not too small
|
||||
if (mScale < SCALE_MIN) {
|
||||
mScale = SCALE_MIN;
|
||||
targetWidth = textureWidth * SCALE_MIN;
|
||||
targetHeight = textureHeight * SCALE_MIN;
|
||||
} else if (mScale > SCALE_MAX) {
|
||||
mScale = SCALE_MAX;
|
||||
targetWidth = textureWidth * SCALE_MAX;
|
||||
targetHeight = textureHeight * SCALE_MAX;
|
||||
}
|
||||
|
||||
// Set mDst.left and mDst.right
|
||||
RectF dst = mDst;
|
||||
switch (startPosition) {
|
||||
case START_POSITION_TOP_LEFT:
|
||||
dst.left = 0;
|
||||
dst.top = 0;
|
||||
break;
|
||||
case START_POSITION_TOP_RIGHT:
|
||||
dst.left = screenWidth - targetWidth;
|
||||
dst.top = 0;
|
||||
break;
|
||||
case START_POSITION_BOTTOM_LEFT:
|
||||
dst.left = 0;
|
||||
dst.top = screenHeight - targetHeight;
|
||||
break;
|
||||
case START_POSITION_BOTTOM_RIGHT:
|
||||
dst.left = screenWidth - targetWidth;
|
||||
dst.top = screenHeight - targetHeight;
|
||||
break;
|
||||
case START_POSITION_CENTER:
|
||||
default:
|
||||
dst.left = (screenWidth - targetWidth) / 2;
|
||||
dst.top = (screenHeight - targetHeight) / 2;
|
||||
break;
|
||||
}
|
||||
|
||||
// Set mDst.right and mDst.bottom
|
||||
dst.right = dst.left + targetWidth;
|
||||
dst.bottom = dst.top + targetHeight;
|
||||
|
||||
// adjust position
|
||||
adjustPosition();
|
||||
|
||||
mScaleOffsetDirty = false;
|
||||
mPositionInRootDirty = true;
|
||||
}
|
||||
|
||||
public void scroll(int dx, int dy, int[] remain) {
|
||||
// Only work after layout
|
||||
if (mScaleOffsetDirty) {
|
||||
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
|
||||
}
|
||||
if (mScaleOffsetDirty) {
|
||||
remain[0] = dx;
|
||||
remain[1] = dy;
|
||||
return;
|
||||
}
|
||||
|
||||
RectF dst = mDst;
|
||||
int screenWidth = getWidth();
|
||||
int screenHeight = getHeight();
|
||||
float targetWidth = dst.width();
|
||||
float targetHeight = dst.height();
|
||||
|
||||
if (targetWidth > screenWidth) {
|
||||
dst.left -= dx;
|
||||
dst.right -= dx;
|
||||
|
||||
float fixXOffset = dst.left;
|
||||
if (fixXOffset > 0) {
|
||||
dst.left -= fixXOffset;
|
||||
dst.right -= fixXOffset;
|
||||
remain[0] = -(int) fixXOffset;
|
||||
} else if ((fixXOffset = screenWidth - dst.right) > 0) {
|
||||
dst.left += fixXOffset;
|
||||
dst.right += fixXOffset;
|
||||
remain[0] = (int) fixXOffset;
|
||||
} else {
|
||||
remain[0] = 0;
|
||||
}
|
||||
} else {
|
||||
remain[0] = dx;
|
||||
}
|
||||
if (targetHeight > screenHeight) {
|
||||
dst.top -= dy;
|
||||
dst.bottom -= dy;
|
||||
|
||||
float fixYOffset = dst.top;
|
||||
if (fixYOffset > 0) {
|
||||
dst.top -= fixYOffset;
|
||||
dst.bottom -= fixYOffset;
|
||||
remain[1] = -(int) fixYOffset;
|
||||
} else if ((fixYOffset = screenHeight - dst.bottom) > 0) {
|
||||
dst.top += fixYOffset;
|
||||
dst.bottom += fixYOffset;
|
||||
remain[1] = (int) fixYOffset;
|
||||
} else {
|
||||
remain[1] = 0;
|
||||
}
|
||||
} else {
|
||||
remain[1] = dy;
|
||||
}
|
||||
|
||||
if (dx != remain[0] || dy != remain[1]) {
|
||||
mPositionInRootDirty = true;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public void scale(float focusX, float focusY, float scale) {
|
||||
// Only work after layout
|
||||
if (mScaleOffsetDirty) {
|
||||
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
|
||||
}
|
||||
if (mScaleOffsetDirty) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((mScale == SCALE_MAX && scale >= 1.0f) || (mScale == SCALE_MIN && scale < 1.0f)) {
|
||||
return;
|
||||
}
|
||||
|
||||
float newScale = mScale * scale;
|
||||
newScale = MathUtils.clamp(newScale, SCALE_MIN, SCALE_MAX);
|
||||
mScale = newScale;
|
||||
RectF dst = mDst;
|
||||
float left = (focusX - ((focusX - dst.left) * scale));
|
||||
float top = (focusY - ((focusY - dst.top) * scale));
|
||||
dst.set(left, top,
|
||||
(left + (mImageTexture.getWidth() * newScale)),
|
||||
(top + (mImageTexture.getHeight() * newScale)));
|
||||
|
||||
// adjust position
|
||||
adjustPosition();
|
||||
|
||||
mPositionInRootDirty = true;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
private void applyPositionInRoot() {
|
||||
int width = mImageTexture.getWidth();
|
||||
int height = mImageTexture.getHeight();
|
||||
RectF dst = mDst;
|
||||
RectF dstActual = mDstActual;
|
||||
RectF srcActual = mSrcActual;
|
||||
|
||||
dstActual.set(dst);
|
||||
getValidRect(mValidRect);
|
||||
if (dstActual.intersect(mValidRect.left, mValidRect.top, mValidRect.right, mValidRect.bottom)) {
|
||||
srcActual.left = MathUtils.lerp(0, width,
|
||||
MathUtils.delerp(dst.left, dst.right, dstActual.left));
|
||||
srcActual.right = MathUtils.lerp(0, width,
|
||||
MathUtils.delerp(dst.left, dst.right, dstActual.right));
|
||||
srcActual.top = MathUtils.lerp(0, height,
|
||||
MathUtils.delerp(dst.top, dst.bottom, dstActual.top));
|
||||
srcActual.bottom = MathUtils.lerp(0, height,
|
||||
MathUtils.delerp(dst.top, dst.bottom, dstActual.bottom));
|
||||
} else {
|
||||
// Can't be seen, set src and dst empty
|
||||
srcActual.setEmpty();
|
||||
dstActual.setEmpty();
|
||||
}
|
||||
|
||||
mPositionInRootDirty = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRender(GLCanvas canvas) {
|
||||
Texture texture = mImageTexture;
|
||||
if (texture == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mScaleOffsetDirty) {
|
||||
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
|
||||
}
|
||||
|
||||
if (mPositionInRootDirty) {
|
||||
applyPositionInRoot();
|
||||
}
|
||||
|
||||
if (!mSrcActual.isEmpty()) {
|
||||
texture.draw(canvas, mSrcActual, mDstActual);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateImageTexture(ImageTexture who) {
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
@@ -1,963 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glgallery;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.util.Log;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.hippo.glview.anim.Animation;
|
||||
import com.hippo.glview.view.GLView;
|
||||
import com.hippo.glview.widget.GLProgressView;
|
||||
import com.hippo.glview.widget.GLTextureView;
|
||||
import com.hippo.yorozuya.AnimationUtils;
|
||||
import com.hippo.yorozuya.AssertUtils;
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
class PagerLayoutManager extends GalleryView.LayoutManager {
|
||||
|
||||
public static final int MODE_LEFT_TO_RIGHT = 0;
|
||||
public static final int MODE_RIGHT_TO_LEFT = 1;
|
||||
private static final String TAG = PagerLayoutManager.class.getSimpleName();
|
||||
private static final Interpolator SMOOTH_SCROLLER_INTERPOLATOR = new Interpolator() {
|
||||
@Override
|
||||
public float getInterpolation(float t) {
|
||||
t -= 1.0f;
|
||||
return t * t * t * t * t + 1.0f;
|
||||
}
|
||||
};
|
||||
private final int[] mScrollRemain = new int[2];
|
||||
private final float[] mScaleDefault = new float[4];
|
||||
private final SmoothScroller mSmoothScroller;
|
||||
private final PageFling mPageFling;
|
||||
private final SmoothScaler mSmoothScaler;
|
||||
private final Rect mTempRect = new Rect();
|
||||
private GalleryView.Adapter mAdapter;
|
||||
private GLProgressView mProgress;
|
||||
private String mErrorStr;
|
||||
private GLTextureView mErrorView;
|
||||
private GalleryPageView mPrevious;
|
||||
private GalleryPageView mCurrent;
|
||||
private GalleryPageView mNext;
|
||||
@Mode
|
||||
private int mMode = MODE_RIGHT_TO_LEFT;
|
||||
private int mScaleMode;
|
||||
private int mStartPosition;
|
||||
private float mScaleValue;
|
||||
private int mOffset;
|
||||
private int mDeltaX;
|
||||
private int mDeltaY;
|
||||
private boolean mCanScrollBetweenPages = false;
|
||||
private boolean mStopAnimationFinger;
|
||||
private int mInterval;
|
||||
// Current index
|
||||
private int mIndex;
|
||||
|
||||
public PagerLayoutManager(Context context, @NonNull GalleryView galleryView,
|
||||
int scaleMode, int startPoint, float scaleValue, int interval) {
|
||||
super(galleryView);
|
||||
|
||||
mScaleMode = scaleMode;
|
||||
mStartPosition = startPoint;
|
||||
mScaleValue = scaleValue;
|
||||
|
||||
mInterval = interval;
|
||||
mSmoothScroller = new SmoothScroller();
|
||||
mPageFling = new PageFling(context);
|
||||
mSmoothScaler = new SmoothScaler();
|
||||
}
|
||||
|
||||
public void setInterval(int interval) {
|
||||
if (mInterval == interval) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mAdapter != null) {
|
||||
int index = getInternalCurrentIndex();
|
||||
GalleryView.Adapter adapter = onDetach();
|
||||
mInterval = interval;
|
||||
onAttach(adapter);
|
||||
setCurrentIndex(index);
|
||||
mGalleryView.requestFill();
|
||||
} else {
|
||||
mInterval = interval;
|
||||
}
|
||||
}
|
||||
|
||||
private void resetParameters() {
|
||||
mOffset = 0;
|
||||
mDeltaX = 0;
|
||||
mDeltaY = 0;
|
||||
mCanScrollBetweenPages = false;
|
||||
mStopAnimationFinger = false;
|
||||
}
|
||||
|
||||
private boolean cancelAllAnimations() {
|
||||
boolean running = mSmoothScroller.isRunning() ||
|
||||
mPageFling.isRunning() ||
|
||||
mSmoothScaler.isRunning();
|
||||
mSmoothScroller.cancel();
|
||||
mPageFling.cancel();
|
||||
mSmoothScaler.cancel();
|
||||
return running;
|
||||
}
|
||||
|
||||
public void setMode(@Mode int mode) {
|
||||
if (mMode == mode) {
|
||||
return;
|
||||
}
|
||||
|
||||
mMode = mode;
|
||||
if (mAdapter != null) {
|
||||
// It is attached, refill
|
||||
// Cancel all animations
|
||||
cancelAllAnimations();
|
||||
// Remove all view
|
||||
removeProgress();
|
||||
removeErrorView();
|
||||
removeAllPages();
|
||||
// Reset parameters
|
||||
resetParameters();
|
||||
// Request fill
|
||||
mGalleryView.requestFill();
|
||||
}
|
||||
}
|
||||
|
||||
public void setScaleMode(int scaleMode) {
|
||||
if (mScaleMode == scaleMode) {
|
||||
return;
|
||||
}
|
||||
mScaleMode = scaleMode;
|
||||
|
||||
if (mCurrent != null) {
|
||||
mCurrent.getImageView().setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
|
||||
}
|
||||
if (mPrevious != null) {
|
||||
mPrevious.getImageView().setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
|
||||
}
|
||||
if (mNext != null) {
|
||||
mNext.getImageView().setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
|
||||
}
|
||||
}
|
||||
|
||||
public void setStartPosition(int startPosition) {
|
||||
if (mStartPosition == startPosition) {
|
||||
return;
|
||||
}
|
||||
mStartPosition = startPosition;
|
||||
|
||||
if (mCurrent != null) {
|
||||
mCurrent.getImageView().setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
|
||||
}
|
||||
if (mPrevious != null) {
|
||||
mPrevious.getImageView().setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
|
||||
}
|
||||
if (mNext != null) {
|
||||
mNext.getImageView().setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(GalleryView.Adapter adapter) {
|
||||
AssertUtils.assertNull("The PagerLayoutManager is attached", mAdapter);
|
||||
AssertUtils.assertNotNull("The adapter is null", adapter);
|
||||
mAdapter = adapter;
|
||||
// Reset parameters
|
||||
resetParameters();
|
||||
}
|
||||
|
||||
private void removeProgress() {
|
||||
if (mProgress != null) {
|
||||
mGalleryView.removeComponent(mProgress);
|
||||
mGalleryView.releaseProgress(mProgress);
|
||||
mProgress = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void removeErrorView() {
|
||||
if (mErrorView != null) {
|
||||
mGalleryView.removeComponent(mErrorView);
|
||||
mGalleryView.releaseErrorView(mErrorView);
|
||||
mErrorView = null;
|
||||
mErrorStr = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void removePage(@NonNull GalleryPageView page) {
|
||||
mGalleryView.removeComponent(page);
|
||||
mAdapter.unbind(page);
|
||||
mGalleryView.releasePage(page);
|
||||
}
|
||||
|
||||
private void removeAllPages() {
|
||||
// Remove gallery view
|
||||
if (mPrevious != null) {
|
||||
removePage(mPrevious);
|
||||
mPrevious = null;
|
||||
}
|
||||
if (mCurrent != null) {
|
||||
removePage(mCurrent);
|
||||
mCurrent = null;
|
||||
}
|
||||
if (mNext != null) {
|
||||
removePage(mNext);
|
||||
mNext = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public GalleryView.Adapter onDetach() {
|
||||
AssertUtils.assertNotNull("The PagerLayoutManager is not attached", mAdapter);
|
||||
|
||||
// Cancel all animations
|
||||
cancelAllAnimations();
|
||||
|
||||
// Remove all view
|
||||
removeProgress();
|
||||
removeErrorView();
|
||||
removeAllPages();
|
||||
|
||||
// Clear iterator
|
||||
GalleryView.Adapter adapter = mAdapter;
|
||||
mAdapter = null;
|
||||
|
||||
return adapter;
|
||||
}
|
||||
|
||||
private GalleryPageView getLeftPage() {
|
||||
switch (mMode) {
|
||||
case MODE_LEFT_TO_RIGHT:
|
||||
return mPrevious;
|
||||
default:
|
||||
case MODE_RIGHT_TO_LEFT:
|
||||
return mNext;
|
||||
}
|
||||
}
|
||||
|
||||
private GalleryPageView getRightPage() {
|
||||
switch (mMode) {
|
||||
case MODE_LEFT_TO_RIGHT:
|
||||
return mNext;
|
||||
default:
|
||||
case MODE_RIGHT_TO_LEFT:
|
||||
return mPrevious;
|
||||
}
|
||||
}
|
||||
|
||||
private GalleryPageView obtainPage() {
|
||||
GalleryPageView page = mGalleryView.obtainPage();
|
||||
page.getImageView().setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
|
||||
return page;
|
||||
}
|
||||
|
||||
private void layoutPage(GalleryPageView page, int widthSpec, int heightSpec,
|
||||
int left, int top, int right, int bottom) {
|
||||
Rect rect = mTempRect;
|
||||
page.getValidRect(rect);
|
||||
boolean oldValid = !rect.isEmpty();
|
||||
page.measure(widthSpec, heightSpec);
|
||||
page.layout(left, top, right, bottom);
|
||||
page.getValidRect(rect);
|
||||
boolean newValid = !rect.isEmpty();
|
||||
if (!oldValid && newValid) {
|
||||
page.getImageView().setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFill() {
|
||||
GalleryView.Adapter adapter = mAdapter;
|
||||
GalleryView galleryView = mGalleryView;
|
||||
AssertUtils.assertNotNull("The PagerLayoutManager is not attached", adapter);
|
||||
|
||||
int width = galleryView.getWidth();
|
||||
int height = galleryView.getHeight();
|
||||
int size = adapter.size();
|
||||
String errorStr = adapter.getError();
|
||||
|
||||
if (size == GalleryProvider.STATE_WAIT) { // Wait here, show progress bar
|
||||
// Remove error view and all pages
|
||||
removeErrorView();
|
||||
removeAllPages();
|
||||
|
||||
// Ensure progress
|
||||
if (mProgress == null) {
|
||||
mProgress = galleryView.obtainProgress();
|
||||
galleryView.addComponent(mProgress);
|
||||
}
|
||||
|
||||
// Place progress center
|
||||
placeCenter(mProgress);
|
||||
} else if (size <= GalleryProvider.STATE_ERROR || size == 0) { // Get error or empty, show error text
|
||||
// Ensure error is not null
|
||||
if (0 == size) {
|
||||
errorStr = galleryView.getEmptyStr();
|
||||
} else if (null == errorStr) {
|
||||
errorStr = galleryView.getDefaultErrorStr();
|
||||
}
|
||||
|
||||
// Remove progress and all pages
|
||||
removeProgress();
|
||||
removeAllPages();
|
||||
|
||||
// Ensure error view
|
||||
if (mErrorView == null) {
|
||||
mErrorView = galleryView.obtainErrorView();
|
||||
galleryView.addComponent(mErrorView);
|
||||
}
|
||||
|
||||
// Update error string
|
||||
if (!errorStr.equals(mErrorStr)) {
|
||||
mErrorStr = errorStr;
|
||||
galleryView.bindErrorView(mErrorView, errorStr);
|
||||
}
|
||||
|
||||
// Place error view center
|
||||
placeCenter(mErrorView);
|
||||
} else {
|
||||
// Remove progress and error view
|
||||
removeProgress();
|
||||
removeErrorView();
|
||||
|
||||
// Ensure index in range
|
||||
int index = mIndex;
|
||||
if (index < 0) {
|
||||
index = 0;
|
||||
mIndex = index;
|
||||
removeAllPages();
|
||||
Log.e(TAG, "index < 0, index = " + index);
|
||||
} else if (index >= size) {
|
||||
index = size - 1;
|
||||
mIndex = index;
|
||||
removeAllPages();
|
||||
Log.e(TAG, "index >= size, index = " + index + ", size = " + size);
|
||||
}
|
||||
|
||||
// Ensure pages
|
||||
if (mCurrent == null) {
|
||||
mCurrent = obtainPage();
|
||||
galleryView.addComponent(mCurrent);
|
||||
adapter.bind(mCurrent, index);
|
||||
}
|
||||
if (mPrevious == null && index > 0) {
|
||||
mPrevious = obtainPage();
|
||||
galleryView.addComponent(mPrevious);
|
||||
adapter.bind(mPrevious, index - 1);
|
||||
} else if (mPrevious != null && index == 0) {
|
||||
removePage(mPrevious);
|
||||
}
|
||||
if (mNext == null && index < size - 1) {
|
||||
mNext = obtainPage();
|
||||
galleryView.addComponent(mNext);
|
||||
adapter.bind(mNext, index + 1);
|
||||
} else if (mNext != null && index == size - 1) {
|
||||
removePage(mNext);
|
||||
}
|
||||
|
||||
GalleryPageView leftPage = getLeftPage();
|
||||
GalleryPageView rightPage = getRightPage();
|
||||
|
||||
// Fix offset
|
||||
final int min = rightPage == null ? 0 : -width - mInterval + 1;
|
||||
final int max = leftPage == null ? 0 : width + mInterval - 1;
|
||||
mOffset = MathUtils.clamp(mOffset, min, max);
|
||||
|
||||
// Measure and layout pages
|
||||
final int offset = mOffset;
|
||||
final int widthSpec = GLView.MeasureSpec.makeMeasureSpec(width, GLView.MeasureSpec.EXACTLY);
|
||||
final int heightSpec = GLView.MeasureSpec.makeMeasureSpec(height, GLView.MeasureSpec.EXACTLY);
|
||||
if (mCurrent != null) {
|
||||
layoutPage(mCurrent, widthSpec, heightSpec,
|
||||
offset, 0, width + offset, height);
|
||||
}
|
||||
if (leftPage != null) {
|
||||
layoutPage(leftPage, widthSpec, heightSpec,
|
||||
-mInterval - width + offset, 0, -mInterval + offset, height);
|
||||
}
|
||||
if (rightPage != null) {
|
||||
layoutPage(rightPage, widthSpec, heightSpec,
|
||||
width + mInterval + offset, 0, width + mInterval + width + offset, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDown() {
|
||||
mDeltaX = 0;
|
||||
mDeltaY = 0;
|
||||
mStopAnimationFinger = cancelAllAnimations();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUp() {
|
||||
if (mCurrent == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Scroll
|
||||
if (mOffset != 0) {
|
||||
int width = mGalleryView.getWidth();
|
||||
int dx;
|
||||
if (mOffset >= mInterval && getLeftPage() != null) {
|
||||
dx = mOffset - width - mInterval;
|
||||
} else if (mOffset <= -mInterval && getRightPage() != null) {
|
||||
dx = mOffset + width + mInterval;
|
||||
} else {
|
||||
dx = mOffset;
|
||||
}
|
||||
final float pageDelta = 7 * (float) Math.abs(mOffset) / (width + mInterval);
|
||||
int duration = (int) ((pageDelta + 1) * 100);
|
||||
mSmoothScroller.startSmoothScroll(dx, 0, duration);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDoubleTapConfirmed(float x, float y) {
|
||||
if (mCurrent == null || !mCurrent.getImageView().isLoaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
float[] scales = mScaleDefault;
|
||||
ImageView image = mCurrent.getImageView();
|
||||
image.getScaleDefault(scales);
|
||||
float scale = image.getScale();
|
||||
float endScale = scales[0];
|
||||
for (float value : scales) {
|
||||
if (scale < value - 0.01f) {
|
||||
endScale = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mSmoothScaler.startSmoothScaler(x, y, scale, endScale, 300);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLongPress(float x, float y) {
|
||||
}
|
||||
|
||||
private void pagePrevious() {
|
||||
if (mIndex <= 0) {
|
||||
return;
|
||||
}
|
||||
mIndex--;
|
||||
|
||||
if (mNext != null) {
|
||||
removePage(mNext);
|
||||
}
|
||||
mNext = mCurrent;
|
||||
mCurrent = mPrevious;
|
||||
mPrevious = null;
|
||||
|
||||
if (mIndex > 0) {
|
||||
mPrevious = obtainPage();
|
||||
mGalleryView.addComponent(mPrevious);
|
||||
mAdapter.bind(mPrevious, mIndex - 1);
|
||||
}
|
||||
}
|
||||
|
||||
private void pageNext() {
|
||||
GalleryView.Adapter adapter = mAdapter;
|
||||
int size = adapter.size();
|
||||
if (mIndex >= size - 1) {
|
||||
return;
|
||||
}
|
||||
mIndex++;
|
||||
|
||||
if (mPrevious != null) {
|
||||
removePage(mPrevious);
|
||||
}
|
||||
mPrevious = mCurrent;
|
||||
mCurrent = mNext;
|
||||
mNext = null;
|
||||
|
||||
if (mIndex < size - 1) {
|
||||
mNext = obtainPage();
|
||||
mGalleryView.addComponent(mNext);
|
||||
adapter.bind(mNext, mIndex + 1);
|
||||
}
|
||||
}
|
||||
|
||||
private void pageLeft() {
|
||||
switch (mMode) {
|
||||
case MODE_LEFT_TO_RIGHT:
|
||||
pagePrevious();
|
||||
break;
|
||||
default:
|
||||
case MODE_RIGHT_TO_LEFT:
|
||||
pageNext();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void pageRight() {
|
||||
switch (mMode) {
|
||||
case MODE_LEFT_TO_RIGHT:
|
||||
pageNext();
|
||||
break;
|
||||
default:
|
||||
case MODE_RIGHT_TO_LEFT:
|
||||
pagePrevious();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private int scrollBetweenPages(int dx) {
|
||||
GalleryPageView leftPage = getLeftPage();
|
||||
GalleryPageView rightPage = getRightPage();
|
||||
int width = mGalleryView.getWidth();
|
||||
|
||||
int remain;
|
||||
if (dx < 0) { // Try to show left
|
||||
int limit;
|
||||
if (leftPage == null) {
|
||||
limit = 0;
|
||||
} else {
|
||||
limit = width + mInterval;
|
||||
}
|
||||
|
||||
if (dx > mOffset - limit) {
|
||||
remain = 0;
|
||||
mOffset -= dx;
|
||||
} else {
|
||||
// Go to left page if left page not null
|
||||
if (leftPage != null) {
|
||||
pageLeft();
|
||||
}
|
||||
remain = dx + limit - mOffset;
|
||||
mOffset = 0;
|
||||
}
|
||||
} else { // Try to show right
|
||||
int limit;
|
||||
if (rightPage == null) {
|
||||
limit = 0;
|
||||
} else {
|
||||
limit = -width - mInterval;
|
||||
}
|
||||
|
||||
if (dx < mOffset - limit) {
|
||||
remain = 0;
|
||||
mOffset -= dx;
|
||||
} else {
|
||||
// Go to right page if right page not null
|
||||
if (rightPage != null) {
|
||||
pageRight();
|
||||
}
|
||||
remain = dx + limit - mOffset;
|
||||
mOffset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return remain;
|
||||
}
|
||||
|
||||
public void scrollInternal(float dx, float dy, float x, float y) {
|
||||
if (mCurrent == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean needFill = false;
|
||||
boolean canImageScroll = true;
|
||||
int remainX = (int) dx;
|
||||
int remainY = (int) dy;
|
||||
|
||||
if (mGalleryView.isFirstScroll()) {
|
||||
mCanScrollBetweenPages = Math.abs(dx) > Math.abs(dy) * 1.5;
|
||||
}
|
||||
|
||||
while (remainX != 0 || remainY != 0) {
|
||||
if (mOffset == 0 && canImageScroll) {
|
||||
ImageView image = mCurrent.getImageView();
|
||||
image.scroll(remainX, remainY, mScrollRemain);
|
||||
remainX = mScrollRemain[0];
|
||||
remainY = mScrollRemain[1];
|
||||
canImageScroll = false;
|
||||
mDeltaX = 0;
|
||||
mDeltaY = 0;
|
||||
} else if (remainX == 0 ||
|
||||
(getLeftPage() == null && mOffset == 0 && remainX < 0) ||
|
||||
(getRightPage() == null && mOffset == 0 && remainX > 0)) {
|
||||
// On edge
|
||||
remainX = 0;
|
||||
remainY = 0;
|
||||
} else if (mCanScrollBetweenPages) {
|
||||
remainX = scrollBetweenPages(remainX);
|
||||
canImageScroll = true;
|
||||
needFill = true;
|
||||
mDeltaX = 0;
|
||||
mDeltaY = 0;
|
||||
} else {
|
||||
remainX = 0;
|
||||
remainY = 0;
|
||||
mDeltaX = 0;
|
||||
mDeltaY = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (needFill) {
|
||||
mGalleryView.requestFill();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScroll(float dx, float dy, float totalX, float totalY, float x, float y) {
|
||||
scrollInternal(dx, dy, x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFling(float velocityX, float velocityY) {
|
||||
if (mCurrent == null || mOffset != 0 || !mCurrent.getImageView().isLoaded() ||
|
||||
!mCurrent.getImageView().canFling()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImageView image = mCurrent.getImageView();
|
||||
mPageFling.startFling((int) velocityX, image.getMinDx(), image.getMaxDx(),
|
||||
(int) velocityY, image.getMinDy(), image.getMaxDy());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canScale() {
|
||||
return mCurrent != null && mOffset == 0 && mCurrent.getImageView().isLoaded();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScale(float focusX, float focusY, float scale) {
|
||||
if (mCurrent == null || !mCurrent.getImageView().isLoaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
mCurrent.getImageView().scale(focusX, focusY, scale);
|
||||
mScaleValue = mCurrent.getImageView().getScale();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onUpdateAnimation(long time) {
|
||||
boolean invalidate = mSmoothScroller.calculate(time);
|
||||
invalidate |= mPageFling.calculate(time);
|
||||
invalidate |= mSmoothScaler.calculate(time);
|
||||
return invalidate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDataChanged() {
|
||||
AssertUtils.assertNotNull("The PagerLayoutManager is not attached", mAdapter);
|
||||
|
||||
// Cancel all animations
|
||||
cancelAllAnimations();
|
||||
// Remove all views
|
||||
removeProgress();
|
||||
removeErrorView();
|
||||
removeAllPages();
|
||||
// Reset parameters
|
||||
resetParameters();
|
||||
mGalleryView.requestFill();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageLeft() {
|
||||
int size = mAdapter.size();
|
||||
if (size <= 0 || mCurrent == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mMode == MODE_LEFT_TO_RIGHT) {
|
||||
if (mIndex != 0) {
|
||||
setCurrentIndex(mIndex - 1);
|
||||
}
|
||||
} else {
|
||||
if (mIndex < size - 1) {
|
||||
setCurrentIndex(mIndex + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageRight() {
|
||||
int size = mAdapter.size();
|
||||
if (size <= 0 || mCurrent == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mMode == MODE_LEFT_TO_RIGHT) {
|
||||
if (mIndex < size - 1) {
|
||||
setCurrentIndex(mIndex + 1);
|
||||
}
|
||||
} else {
|
||||
if (mIndex != 0) {
|
||||
setCurrentIndex(mIndex - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTapOrPressEnable() {
|
||||
return !mStopAnimationFinger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GalleryPageView findPageByIndex(int index) {
|
||||
if (mCurrent != null && mCurrent.getIndex() == index) {
|
||||
return mCurrent;
|
||||
}
|
||||
if (mPrevious != null && mPrevious.getIndex() == index) {
|
||||
return mPrevious;
|
||||
}
|
||||
if (mNext != null && mNext.getIndex() == index) {
|
||||
return mNext;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentIndex() {
|
||||
if (mCurrent != null) {
|
||||
return mCurrent.getIndex();
|
||||
} else {
|
||||
return GalleryPageView.INVALID_INDEX;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCurrentIndex(int index) {
|
||||
int size = mAdapter.size();
|
||||
if (size <= 0) {
|
||||
// Can't get size now, assume size is MAX
|
||||
size = Integer.MAX_VALUE;
|
||||
}
|
||||
if (index == mIndex || index < 0 || index >= size) {
|
||||
return;
|
||||
}
|
||||
if (mCurrent == null) {
|
||||
mIndex = index;
|
||||
} else if (index == mIndex - 1) {
|
||||
// Cancel all animations
|
||||
cancelAllAnimations();
|
||||
// Reset parameters
|
||||
resetParameters();
|
||||
// Go to previous
|
||||
pagePrevious();
|
||||
// Request fill
|
||||
mGalleryView.requestFill();
|
||||
} else if (index == mIndex + 1) {
|
||||
// Cancel all animations
|
||||
cancelAllAnimations();
|
||||
// Reset parameters
|
||||
resetParameters();
|
||||
// Go to next
|
||||
pageNext();
|
||||
// Request fill
|
||||
mGalleryView.requestFill();
|
||||
} else {
|
||||
mIndex = index;
|
||||
// It is attached, refill
|
||||
// Cancel all animations
|
||||
cancelAllAnimations();
|
||||
// Remove all view
|
||||
removeProgress();
|
||||
removeErrorView();
|
||||
removeAllPages();
|
||||
// Reset parameters
|
||||
resetParameters();
|
||||
// Request fill
|
||||
mGalleryView.requestFill();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIndexUnder(float x, float y) {
|
||||
if (mCurrent == null) {
|
||||
return GalleryPageView.INVALID_INDEX;
|
||||
} else {
|
||||
int intX = (int) x;
|
||||
int intY = (int) y;
|
||||
if (mCurrent.bounds().contains(intX, intY)) {
|
||||
return mCurrent.getIndex();
|
||||
} else if (mPrevious != null && mPrevious.bounds().contains(intX, intY)) {
|
||||
return mPrevious.getIndex();
|
||||
} else if (mNext != null && mNext.bounds().contains(intX, intY)) {
|
||||
return mNext.getIndex();
|
||||
} else {
|
||||
return GalleryPageView.INVALID_INDEX;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
int getInternalCurrentIndex() {
|
||||
int currentIndex = getCurrentIndex();
|
||||
if (currentIndex == GalleryPageView.INVALID_INDEX) {
|
||||
currentIndex = mIndex;
|
||||
}
|
||||
return currentIndex;
|
||||
}
|
||||
|
||||
@IntDef({MODE_LEFT_TO_RIGHT, MODE_RIGHT_TO_LEFT})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
private @interface Mode {
|
||||
}
|
||||
|
||||
private class SmoothScroller extends Animation {
|
||||
|
||||
private int mDx;
|
||||
private int mDy;
|
||||
private int mLastX;
|
||||
private int mLastY;
|
||||
|
||||
public SmoothScroller() {
|
||||
setInterpolator(SMOOTH_SCROLLER_INTERPOLATOR);
|
||||
}
|
||||
|
||||
public void startSmoothScroll(int dx, int dy, int duration) {
|
||||
mDx = dx;
|
||||
mDy = dy;
|
||||
mLastX = 0;
|
||||
mLastY = 0;
|
||||
setDuration(duration);
|
||||
start();
|
||||
mGalleryView.invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCalculate(float progress) {
|
||||
int x = (int) (mDx * progress);
|
||||
int y = (int) (mDy * progress);
|
||||
int offsetX = x - mLastX;
|
||||
while (offsetX != 0) {
|
||||
int oldOffsetX = offsetX;
|
||||
offsetX = scrollBetweenPages(offsetX);
|
||||
// Avoid loop infinitely
|
||||
if (offsetX == oldOffsetX) {
|
||||
break;
|
||||
} else {
|
||||
mGalleryView.requestFill();
|
||||
}
|
||||
}
|
||||
mLastX = x;
|
||||
mLastY = y;
|
||||
}
|
||||
}
|
||||
|
||||
private class PageFling extends Fling {
|
||||
|
||||
private final int[] mTemp = new int[2];
|
||||
private int mVelocityX;
|
||||
private int mVelocityY;
|
||||
private int mDx;
|
||||
private int mDy;
|
||||
private int mLastX;
|
||||
private int mLastY;
|
||||
|
||||
public PageFling(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public void startFling(int velocityX, int minX, int maxX,
|
||||
int velocityY, int minY, int maxY) {
|
||||
mVelocityX = velocityX;
|
||||
mVelocityY = velocityY;
|
||||
mDx = (int) (getSplineFlingDistance(velocityX) * Math.signum(velocityX));
|
||||
mDy = (int) (getSplineFlingDistance(velocityY) * Math.signum(velocityY));
|
||||
mLastX = 0;
|
||||
mLastY = 0;
|
||||
int durationX = getSplineFlingDuration(velocityX);
|
||||
int durationY = getSplineFlingDuration(velocityY);
|
||||
|
||||
if (mDx < minX) {
|
||||
durationX = adjustDuration(0, mDx, minX, durationX);
|
||||
mDx = minX;
|
||||
}
|
||||
if (mDx > maxX) {
|
||||
durationX = adjustDuration(0, mDx, maxX, durationX);
|
||||
mDx = maxX;
|
||||
}
|
||||
if (mDy < minY) {
|
||||
durationY = adjustDuration(0, mDy, minY, durationY);
|
||||
mDy = minY;
|
||||
}
|
||||
if (mDy > maxY) {
|
||||
durationY = adjustDuration(0, mDy, maxY, durationY);
|
||||
mDy = maxY;
|
||||
}
|
||||
|
||||
if (mDx == 0 && mDy == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
setDuration(Math.max(durationX, durationY));
|
||||
start();
|
||||
mGalleryView.invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCalculate(float progress) {
|
||||
int x = (int) (mDx * progress);
|
||||
int y = (int) (mDy * progress);
|
||||
int offsetX = x - mLastX;
|
||||
int offsetY = y - mLastY;
|
||||
if (mCurrent != null && (offsetX != 0 || offsetY != 0)) {
|
||||
mCurrent.getImageView().scroll(-offsetX, -offsetY, mTemp);
|
||||
}
|
||||
mLastX = x;
|
||||
mLastY = y;
|
||||
}
|
||||
}
|
||||
|
||||
private class SmoothScaler extends Animation {
|
||||
|
||||
private float mFocusX;
|
||||
private float mFocusY;
|
||||
private float mStartScale;
|
||||
private float mEndScale;
|
||||
private float mLastScale;
|
||||
|
||||
public SmoothScaler() {
|
||||
setInterpolator(AnimationUtils.FAST_SLOW_INTERPOLATOR);
|
||||
}
|
||||
|
||||
public void startSmoothScaler(float focusX, float focusY,
|
||||
float startScale, float endScale, int duration) {
|
||||
mFocusX = focusX;
|
||||
mFocusY = focusY;
|
||||
mStartScale = startScale;
|
||||
mEndScale = endScale;
|
||||
mLastScale = startScale;
|
||||
setDuration(duration);
|
||||
start();
|
||||
mGalleryView.invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCalculate(float progress) {
|
||||
if (mCurrent == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
float scale = MathUtils.lerp(mStartScale, mEndScale, progress);
|
||||
mCurrent.getImageView().scale(mFocusX, mFocusY, scale / mLastScale);
|
||||
mLastScale = scale;
|
||||
mScaleValue = scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,163 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glgallery;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.hippo.glview.image.ImageTexture;
|
||||
import com.hippo.glview.image.ImageWrapper;
|
||||
import com.hippo.glview.view.GLRootView;
|
||||
|
||||
public class SimpleAdapter extends GalleryView.Adapter implements GalleryProvider.Listener {
|
||||
|
||||
private final GalleryProvider mProvider;
|
||||
private final ImageTexture.Uploader mUploader;
|
||||
private boolean mShowIndex = true;
|
||||
|
||||
public SimpleAdapter(@NonNull GLRootView glRootView, @NonNull GalleryProvider provider) {
|
||||
mProvider = provider;
|
||||
mUploader = new ImageTexture.Uploader(glRootView);
|
||||
}
|
||||
|
||||
public void clearUploader() {
|
||||
mUploader.clear();
|
||||
}
|
||||
|
||||
public void setShowIndex(boolean show) {
|
||||
mShowIndex = show;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(GalleryPageView view, int index) {
|
||||
mProvider.request(index);
|
||||
view.showInfo();
|
||||
view.setImage(null);
|
||||
if (mShowIndex) {
|
||||
view.setPage(index + 1);
|
||||
} else {
|
||||
view.hidePage();
|
||||
}
|
||||
view.setProgress(GalleryPageView.PROGRESS_INDETERMINATE);
|
||||
view.setError(null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnbind(GalleryPageView view, int index) {
|
||||
mProvider.cancelRequest(index);
|
||||
view.setImage(null);
|
||||
view.setError(null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getError() {
|
||||
return mProvider.getError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return mProvider.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDataChanged() {
|
||||
mGalleryView.onDataChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageWait(int index) {
|
||||
GalleryPageView page = findPageByIndex(index);
|
||||
if (page != null) {
|
||||
page.showInfo();
|
||||
page.setImage(null);
|
||||
if (mShowIndex) {
|
||||
page.setPage(index + 1);
|
||||
} else {
|
||||
page.hidePage();
|
||||
}
|
||||
page.setProgress(GalleryPageView.PROGRESS_INDETERMINATE);
|
||||
page.setError(null, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPagePercent(int index, float percent) {
|
||||
GalleryPageView page = findPageByIndex(index);
|
||||
if (page != null) {
|
||||
page.showInfo();
|
||||
page.setImage(null);
|
||||
if (mShowIndex) {
|
||||
page.setPage(index + 1);
|
||||
} else {
|
||||
page.hidePage();
|
||||
}
|
||||
page.setProgress(percent);
|
||||
page.setError(null, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageSucceed(int index, ImageWrapper image) {
|
||||
GalleryPageView page = findPageByIndex(index);
|
||||
if (page != null) {
|
||||
if (image.obtain()) {
|
||||
ImageTexture imageTexture = new ImageTexture(image);
|
||||
mUploader.addTexture(imageTexture);
|
||||
page.showImage();
|
||||
page.setImage(imageTexture);
|
||||
if (mShowIndex) {
|
||||
page.setPage(index + 1);
|
||||
} else {
|
||||
page.hidePage();
|
||||
}
|
||||
page.setProgress(GalleryPageView.PROGRESS_GONE);
|
||||
page.setError(null, null);
|
||||
} else {
|
||||
// The image is recycled, request again.
|
||||
// TODO request loop ?
|
||||
mProvider.request(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageFailed(int index, String error) {
|
||||
GalleryPageView page = findPageByIndex(index);
|
||||
if (page != null) {
|
||||
page.showInfo();
|
||||
page.setImage(null);
|
||||
if (mShowIndex) {
|
||||
page.setPage(index + 1);
|
||||
} else {
|
||||
page.hidePage();
|
||||
}
|
||||
page.setProgress(GalleryPageView.PROGRESS_GONE);
|
||||
page.setError(error, mGalleryView);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDataChanged(int index) {
|
||||
GalleryPageView page = findPageByIndex(index);
|
||||
if (page != null) {
|
||||
mProvider.request(index);
|
||||
}
|
||||
}
|
||||
|
||||
private GalleryPageView findPageByIndex(int index) {
|
||||
return mGalleryView != null ? mGalleryView.findPageByIndex(index) : null;
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.anim;
|
||||
|
||||
import com.hippo.glview.glrenderer.GLCanvas;
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
|
||||
public class AlphaAnimation extends CanvasAnimation {
|
||||
private final float mStartAlpha;
|
||||
private final float mEndAlpha;
|
||||
private float mCurrentAlpha;
|
||||
|
||||
public AlphaAnimation(float from, float to) {
|
||||
mStartAlpha = from;
|
||||
mEndAlpha = to;
|
||||
mCurrentAlpha = from;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(GLCanvas canvas) {
|
||||
canvas.multiplyAlpha(mCurrentAlpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCanvasSaveFlags() {
|
||||
return GLCanvas.SAVE_FLAG_ALPHA;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCalculate(float progress) {
|
||||
mCurrentAlpha = MathUtils.clamp(mStartAlpha +
|
||||
(mEndAlpha - mStartAlpha) * progress, 0f, 1f);
|
||||
}
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.anim;
|
||||
|
||||
import android.os.SystemClock;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
|
||||
// Animation calculates a value according to the current input time.
|
||||
//
|
||||
// 1. First we need to use setDuration(int) to set the duration of the
|
||||
// animation. The duration is in milliseconds.
|
||||
// 2. Then we should call start(). The actual start time is the first value
|
||||
// passed to calculate(long).
|
||||
// 3. Each time we want to get an animation value, we call
|
||||
// calculate(long currentTimeMillis) to ask the Animation to calculate it.
|
||||
// The parameter passed to calculate(long) should be nonnegative.
|
||||
// 4. Use get() to get that value.
|
||||
//
|
||||
// In step 3, onCalculate(float progress) is called so subclasses can calculate
|
||||
// the value according to progress (progress is a value in [0,1]).
|
||||
//
|
||||
// Before onCalculate(float) is called, There is an optional interpolator which
|
||||
// can change the progress value. The interpolator can be set by
|
||||
// setInterpolator(Interpolator). If the interpolator is used, the value passed
|
||||
// to onCalculate may be (for example, the overshoot effect).
|
||||
//
|
||||
// The isActive() method returns true after the animation start() is called and
|
||||
// before calculate is passed a value which reaches the duration of the
|
||||
// animation.
|
||||
//
|
||||
// The start() method can be called again to restart the Animation.
|
||||
//
|
||||
abstract public class Animation {
|
||||
/**
|
||||
* When the animation reaches the end and <code>repeatCount</code> is INFINITE
|
||||
* or a positive value, the animation restarts from the beginning.
|
||||
*/
|
||||
public static final int RESTART = 1;
|
||||
/**
|
||||
* When the animation reaches the end and <code>repeatCount</code> is INFINITE
|
||||
* or a positive value, the animation reverses direction on every iteration.
|
||||
*/
|
||||
public static final int REVERSE = 2;
|
||||
/**
|
||||
* This value used used with the {@link #setRepeatCount(int)} property to repeat
|
||||
* the animation indefinitely.
|
||||
*/
|
||||
public static final int INFINITE = -1;
|
||||
private static final long ANIMATION_START = -1;
|
||||
private static final long NO_ANIMATION = -2;
|
||||
private long mStartTime = NO_ANIMATION;
|
||||
private long mDuration;
|
||||
private Interpolator mInterpolator;
|
||||
private int mRepeatCount;
|
||||
private int mRepeatMode;
|
||||
|
||||
private int mRunnedCount;
|
||||
private long mLastFrameTime;
|
||||
|
||||
public void setInterpolator(Interpolator interpolator) {
|
||||
mInterpolator = interpolator;
|
||||
}
|
||||
|
||||
public void setDuration(long duration) {
|
||||
mDuration = duration;
|
||||
}
|
||||
|
||||
public void setRepeatCount(int repeatCount) {
|
||||
mRepeatCount = repeatCount;
|
||||
}
|
||||
|
||||
public void setRepeatMode(int repeatMode) {
|
||||
mRepeatMode = repeatMode;
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return mStartTime > 0 || mStartTime == ANIMATION_START;
|
||||
}
|
||||
|
||||
public long getLastFrameTime() {
|
||||
return mLastFrameTime;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
if (mStartTime == NO_ANIMATION) {
|
||||
mStartTime = ANIMATION_START;
|
||||
mRunnedCount = 0;
|
||||
mLastFrameTime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void startAt(long time) {
|
||||
start();
|
||||
setStartTime(time);
|
||||
}
|
||||
|
||||
public void startNow() {
|
||||
start();
|
||||
setStartTime(SystemClock.uptimeMillis());
|
||||
}
|
||||
|
||||
public void setStartTime(long time) {
|
||||
mStartTime = time;
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
mStartTime = NO_ANIMATION;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
mStartTime = ANIMATION_START;
|
||||
mRunnedCount = 0;
|
||||
mLastFrameTime = 0;
|
||||
}
|
||||
|
||||
// TODO mRepeatMode
|
||||
public boolean calculate(long currentTimeMillis) {
|
||||
if (mStartTime == NO_ANIMATION) {
|
||||
return false;
|
||||
}
|
||||
if (mStartTime == ANIMATION_START) {
|
||||
mStartTime = currentTimeMillis;
|
||||
}
|
||||
mLastFrameTime = currentTimeMillis;
|
||||
|
||||
long elapse = currentTimeMillis - mStartTime;
|
||||
float x = MathUtils.clamp(mDuration == 0.0f ? 1.0f : (float) elapse / mDuration, 0.0f, 1.0f); // Avoid NaN
|
||||
Interpolator i = mInterpolator;
|
||||
onCalculate(i != null ? i.getInterpolation(x) : x);
|
||||
|
||||
// It is ok to call cancel() in onCalculate()
|
||||
if (mStartTime != NO_ANIMATION && elapse >= mDuration) {
|
||||
mRunnedCount++;
|
||||
if (mRunnedCount >= mRepeatCount && mRepeatCount != INFINITE) {
|
||||
onFinish();
|
||||
mStartTime = NO_ANIMATION;
|
||||
} else {
|
||||
mStartTime += elapse;
|
||||
}
|
||||
}
|
||||
|
||||
return mStartTime != NO_ANIMATION;
|
||||
}
|
||||
|
||||
abstract protected void onCalculate(float progress);
|
||||
|
||||
protected void onFinish() {
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.anim;
|
||||
|
||||
import com.hippo.glview.glrenderer.GLCanvas;
|
||||
|
||||
public abstract class CanvasAnimation extends Animation {
|
||||
|
||||
public abstract int getCanvasSaveFlags();
|
||||
|
||||
public abstract void apply(GLCanvas canvas);
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.anim;
|
||||
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
|
||||
public class FloatAnimation extends Animation {
|
||||
|
||||
private float mFrom;
|
||||
private float mTo;
|
||||
private float mCurrent;
|
||||
|
||||
public void setRange(float from, float to) {
|
||||
mFrom = from;
|
||||
mTo = to;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCalculate(float progress) {
|
||||
mCurrent = MathUtils.lerp(mFrom, mTo, progress);
|
||||
}
|
||||
|
||||
public float get() {
|
||||
return mCurrent;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.TYPE})
|
||||
public @interface RenderThread {
|
||||
}
|
||||
@@ -1,214 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.glrenderer;
|
||||
|
||||
import android.graphics.RectF;
|
||||
import android.util.Log;
|
||||
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
// BasicTexture is a Texture corresponds to a real GL texture.
|
||||
// The state of a BasicTexture indicates whether its data is loaded to GL memory.
|
||||
// If a BasicTexture is loaded into GL memory, it has a GL texture id.
|
||||
public abstract class BasicTexture implements Texture {
|
||||
|
||||
protected static final int UNSPECIFIED = -1;
|
||||
protected static final int STATE_UNLOADED = 0;
|
||||
protected static final int STATE_LOADED = 1;
|
||||
protected static final int STATE_ERROR = -1;
|
||||
private static final String TAG = "BasicTexture";
|
||||
// Log a warning if a texture is larger along a dimension
|
||||
private static final int MAX_TEXTURE_SIZE = 4096;
|
||||
private final static WeakHashMap<BasicTexture, Object> sAllTextures
|
||||
= new WeakHashMap<>();
|
||||
private static final ThreadLocal<Class> sInFinalizer = new ThreadLocal<>();
|
||||
protected int mId = -1;
|
||||
protected int mState;
|
||||
protected int mWidth = UNSPECIFIED;
|
||||
protected int mHeight = UNSPECIFIED;
|
||||
protected int mTextureWidth;
|
||||
protected int mTextureHeight;
|
||||
protected GLCanvas mCanvasRef = null;
|
||||
private boolean mHasBorder;
|
||||
|
||||
protected BasicTexture(GLCanvas canvas, int id, int state) {
|
||||
setAssociatedCanvas(canvas);
|
||||
mId = id;
|
||||
mState = state;
|
||||
synchronized (sAllTextures) {
|
||||
sAllTextures.put(this, null);
|
||||
}
|
||||
}
|
||||
|
||||
protected BasicTexture() {
|
||||
this(null, 0, STATE_UNLOADED);
|
||||
}
|
||||
|
||||
// This is for deciding if we can call Bitmap's recycle().
|
||||
// We cannot call Bitmap's recycle() in finalizer because at that point
|
||||
// the finalizer of Bitmap may already be called so recycle() will crash.
|
||||
public static boolean inFinalizer() {
|
||||
return sInFinalizer.get() != null;
|
||||
}
|
||||
|
||||
public static void yieldAllTextures() {
|
||||
synchronized (sAllTextures) {
|
||||
for (BasicTexture t : sAllTextures.keySet()) {
|
||||
t.yield();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void invalidateAllTextures() {
|
||||
synchronized (sAllTextures) {
|
||||
for (BasicTexture t : sAllTextures.keySet()) {
|
||||
t.mState = STATE_UNLOADED;
|
||||
t.setAssociatedCanvas(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void setAssociatedCanvas(GLCanvas canvas) {
|
||||
mCanvasRef = canvas;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the content size of this texture. In OpenGL, the actual texture
|
||||
* size must be of power of 2, the size of the content may be smaller.
|
||||
*/
|
||||
public void setSize(int width, int height) {
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
mTextureWidth = width > 0 ? MathUtils.nextPowerOf2(width) : 0;
|
||||
mTextureHeight = height > 0 ? MathUtils.nextPowerOf2(height) : 0;
|
||||
if (mTextureWidth > MAX_TEXTURE_SIZE || mTextureHeight > MAX_TEXTURE_SIZE) {
|
||||
Log.w(TAG, String.format("texture is too large: %d x %d",
|
||||
mTextureWidth, mTextureHeight), new Exception());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isFlippedVertically() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return mWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return mHeight;
|
||||
}
|
||||
|
||||
// Returns the width rounded to the next power of 2.
|
||||
public int getTextureWidth() {
|
||||
return mTextureWidth;
|
||||
}
|
||||
|
||||
// Returns the height rounded to the next power of 2.
|
||||
public int getTextureHeight() {
|
||||
return mTextureHeight;
|
||||
}
|
||||
|
||||
// Returns true if the texture has one pixel transparent border around the
|
||||
// actual content. This is used to avoid jigged edges.
|
||||
//
|
||||
// The jigged edges appear because we use GL_CLAMP_TO_EDGE for texture wrap
|
||||
// mode (GL_CLAMP is not available in OpenGL ES), so a pixel partially
|
||||
// covered by the texture will use the color of the edge texel. If we add
|
||||
// the transparent border, the color of the edge texel will be mixed with
|
||||
// appropriate amount of transparent.
|
||||
//
|
||||
// Currently our background is black, so we can draw the thumbnails without
|
||||
// enabling blending.
|
||||
public boolean hasBorder() {
|
||||
return mHasBorder;
|
||||
}
|
||||
|
||||
protected void setBorder(boolean hasBorder) {
|
||||
mHasBorder = hasBorder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, int x, int y) {
|
||||
canvas.drawTexture(this, x, y, getWidth(), getHeight());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, int x, int y, int w, int h) {
|
||||
canvas.drawTexture(this, x, y, w, h);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, RectF source, RectF target) {
|
||||
canvas.drawTexture(this, source, target);
|
||||
}
|
||||
|
||||
// onBind is called before GLCanvas binds this texture.
|
||||
// It should make sure the data is uploaded to GL memory.
|
||||
abstract protected boolean onBind(GLCanvas canvas);
|
||||
|
||||
// Returns the GL texture target for this texture (e.g. GL_TEXTURE_2D).
|
||||
abstract protected int getTarget();
|
||||
|
||||
public boolean isLoaded() {
|
||||
return mState == STATE_LOADED;
|
||||
}
|
||||
|
||||
// recycle() is called when the texture will never be used again,
|
||||
// so it can free all resources.
|
||||
public void recycle() {
|
||||
freeResource();
|
||||
}
|
||||
|
||||
// yield() is called when the texture will not be used temporarily,
|
||||
// so it can free some resources.
|
||||
// The default implementation unloads the texture from GL memory, so
|
||||
// the subclass should make sure it can reload the texture to GL memory
|
||||
// later, or it will have to override this method.
|
||||
public void yield() {
|
||||
freeResource();
|
||||
}
|
||||
|
||||
private void freeResource() {
|
||||
GLCanvas canvas = mCanvasRef;
|
||||
if (canvas != null && mId != -1) {
|
||||
canvas.unloadTexture(this);
|
||||
mId = -1; // Don't free it again.
|
||||
}
|
||||
mState = STATE_UNLOADED;
|
||||
setAssociatedCanvas(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
try {
|
||||
sInFinalizer.set(BasicTexture.class);
|
||||
recycle();
|
||||
sInFinalizer.set(null);
|
||||
} finally {
|
||||
super.finalize();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.glrenderer;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import com.hippo.yorozuya.AssertUtils;
|
||||
|
||||
// BitmapTexture is a texture whose content is specified by a fixed Bitmap.
|
||||
//
|
||||
// The texture does not own the Bitmap. The user should make sure the Bitmap
|
||||
// is valid during the texture's lifetime. When the texture is recycled, it
|
||||
// does not free the Bitmap.
|
||||
public class BitmapTexture extends UploadedTexture {
|
||||
protected Bitmap mContentBitmap;
|
||||
|
||||
public BitmapTexture(Bitmap bitmap) {
|
||||
this(bitmap, false);
|
||||
}
|
||||
|
||||
public BitmapTexture(Bitmap bitmap, boolean hasBorder) {
|
||||
super(hasBorder);
|
||||
AssertUtils.assertTrue(bitmap != null && !bitmap.isRecycled());
|
||||
mContentBitmap = bitmap;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFreeBitmap(Bitmap bitmap) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bitmap onGetBitmap() {
|
||||
return mContentBitmap;
|
||||
}
|
||||
|
||||
public Bitmap getBitmap() {
|
||||
return mContentBitmap;
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.glrenderer;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Bitmap.Config;
|
||||
import android.graphics.Canvas;
|
||||
|
||||
// CanvasTexture is a texture whose content is the drawing on a Canvas.
|
||||
// The subclasses should override onDraw() to draw on the bitmap.
|
||||
// By default CanvasTexture is not opaque.
|
||||
abstract class CanvasTexture extends UploadedTexture {
|
||||
private final Config mConfig;
|
||||
|
||||
public CanvasTexture(int width, int height) {
|
||||
mConfig = Config.ARGB_8888;
|
||||
setSize(width, height);
|
||||
setOpaque(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bitmap onGetBitmap() {
|
||||
Bitmap bitmap = Bitmap.createBitmap(mWidth, mHeight, mConfig);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
onDraw(canvas, bitmap);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFreeBitmap(Bitmap bitmap) {
|
||||
if (!inFinalizer()) {
|
||||
bitmap.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
abstract protected void onDraw(Canvas canvas, Bitmap backing);
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.glrenderer;
|
||||
|
||||
import android.graphics.RectF;
|
||||
|
||||
import com.hippo.yorozuya.ColorUtils;
|
||||
|
||||
// ColorTexture is a texture which fills the rectangle with the specified color.
|
||||
public class ColorTexture implements Texture {
|
||||
|
||||
private final int mColor;
|
||||
private int mWidth = 0;
|
||||
private int mHeight = 0;
|
||||
|
||||
public ColorTexture(int color) {
|
||||
mColor = color;
|
||||
}
|
||||
|
||||
private boolean isValidSize() {
|
||||
return mWidth > 0 && mHeight > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, int x, int y) {
|
||||
if (isValidSize()) {
|
||||
draw(canvas, x, y, mWidth, mHeight);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, int x, int y, int w, int h) {
|
||||
canvas.fillRect(x, y, w, h, mColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, RectF source, RectF target) {
|
||||
canvas.fillRect(target.left, target.top, target.width(), target.height(), mColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpaque() {
|
||||
return ColorUtils.isOpaque(mColor);
|
||||
}
|
||||
|
||||
public void setSize(int width, int height) {
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return mWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return mHeight;
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.glrenderer;
|
||||
|
||||
// ExtTexture is a texture whose content comes from a external texture.
|
||||
// Before drawing, setSize() should be called.
|
||||
public class ExtTexture extends BasicTexture {
|
||||
|
||||
private final int mTarget;
|
||||
|
||||
public ExtTexture(GLCanvas canvas, int target) {
|
||||
GLId glId = canvas.getGLId();
|
||||
mId = glId.generateTexture();
|
||||
mTarget = target;
|
||||
}
|
||||
|
||||
private void uploadToCanvas(GLCanvas canvas) {
|
||||
canvas.setTextureParameters(this);
|
||||
setAssociatedCanvas(canvas);
|
||||
mState = STATE_LOADED;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onBind(GLCanvas canvas) {
|
||||
if (!isLoaded()) {
|
||||
uploadToCanvas(canvas);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTarget() {
|
||||
return mTarget;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpaque() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void yield() {
|
||||
// we cannot free the texture because we have no backup.
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.glrenderer;
|
||||
|
||||
|
||||
import android.graphics.RectF;
|
||||
|
||||
// FadeInTexture is a texture which begins with a color, then gradually animates
|
||||
// into a given texture.
|
||||
public class FadeInTexture extends FadeTexture implements Texture {
|
||||
|
||||
private final int mColor;
|
||||
private final TiledTexture mTexture;
|
||||
|
||||
public FadeInTexture(int color, TiledTexture texture) {
|
||||
super(texture.getWidth(), texture.getHeight(), texture.isOpaque());
|
||||
mColor = color;
|
||||
mTexture = texture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, int x, int y, int w, int h) {
|
||||
if (isAnimating()) {
|
||||
mTexture.drawMixed(canvas, mColor, getRatio(), x, y, w, h);
|
||||
} else {
|
||||
mTexture.draw(canvas, x, y, w, h);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, RectF source, RectF target) {
|
||||
if (isAnimating()) {
|
||||
mTexture.drawMixed(canvas, mColor, getRatio(), source, target);
|
||||
} else {
|
||||
mTexture.draw(canvas, source, target);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.glrenderer;
|
||||
|
||||
|
||||
import android.graphics.RectF;
|
||||
|
||||
// FadeOutTexture is a texture which begins with a given texture, then gradually animates
|
||||
// into fading out totally.
|
||||
public class FadeOutTexture extends FadeTexture {
|
||||
|
||||
private final BasicTexture mTexture;
|
||||
|
||||
public FadeOutTexture(BasicTexture texture) {
|
||||
super(texture.getWidth(), texture.getHeight(), texture.isOpaque());
|
||||
mTexture = texture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, int x, int y, int w, int h) {
|
||||
if (isAnimating()) {
|
||||
canvas.save(GLCanvas.SAVE_FLAG_ALPHA);
|
||||
canvas.setAlpha(getRatio());
|
||||
mTexture.draw(canvas, x, y, w, h);
|
||||
canvas.restore();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, RectF source, RectF target) {
|
||||
if (isAnimating()) {
|
||||
canvas.save(GLCanvas.SAVE_FLAG_ALPHA);
|
||||
canvas.setAlpha(getRatio());
|
||||
mTexture.draw(canvas, source, target);
|
||||
canvas.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.glrenderer;
|
||||
|
||||
import com.hippo.glview.view.AnimationTime;
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
|
||||
// FadeTexture is a texture which fades the given texture along the time.
|
||||
public abstract class FadeTexture implements Texture {
|
||||
|
||||
// The duration of the fading animation in milliseconds
|
||||
public static final int DURATION = 180;
|
||||
|
||||
private final long mStartTime;
|
||||
private final int mWidth;
|
||||
private final int mHeight;
|
||||
private final boolean mIsOpaque;
|
||||
private boolean mIsAnimating;
|
||||
|
||||
public FadeTexture(int width, int height, boolean opaque) {
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
mIsOpaque = opaque;
|
||||
mStartTime = now();
|
||||
mIsAnimating = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, int x, int y) {
|
||||
draw(canvas, x, y, mWidth, mHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpaque() {
|
||||
return mIsOpaque;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return mWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return mHeight;
|
||||
}
|
||||
|
||||
public boolean isAnimating() {
|
||||
if (mIsAnimating) {
|
||||
if (now() - mStartTime >= DURATION) {
|
||||
mIsAnimating = false;
|
||||
}
|
||||
}
|
||||
return mIsAnimating;
|
||||
}
|
||||
|
||||
protected float getRatio() {
|
||||
float r = (float) (now() - mStartTime) / DURATION;
|
||||
return MathUtils.clamp(1.0f - r, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
private long now() {
|
||||
return AnimationTime.get();
|
||||
}
|
||||
}
|
||||
@@ -1,223 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.glrenderer;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
|
||||
//
|
||||
// GLCanvas gives a convenient interface to draw using OpenGL.
|
||||
//
|
||||
// When a rectangle is specified in this interface, it means the region
|
||||
// [x, x+width) * [y, y+height)
|
||||
//
|
||||
public interface GLCanvas {
|
||||
|
||||
int SAVE_FLAG_ALL = 0xFFFFFFFF;
|
||||
int SAVE_FLAG_ALPHA = 0x01;
|
||||
int SAVE_FLAG_MATRIX = 0x02;
|
||||
|
||||
GLId getGLId();
|
||||
|
||||
// Tells GLCanvas the size of the underlying GL surface. This should be
|
||||
// called before first drawing and when the size of GL surface is changed.
|
||||
// This is called by GLRoot and should not be called by the clients
|
||||
// who only want to draw on the GLCanvas. Both width and height must be
|
||||
// nonnegative.
|
||||
void setSize(int width, int height);
|
||||
|
||||
// Clear the drawing buffers. This should only be used by GLRoot.
|
||||
void clearBuffer();
|
||||
|
||||
void clearBuffer(float[] argb);
|
||||
|
||||
float getAlpha();
|
||||
|
||||
// Sets and gets the current alpha, alpha must be in [0, 1].
|
||||
void setAlpha(float alpha);
|
||||
|
||||
// (current alpha) = (current alpha) * alpha
|
||||
void multiplyAlpha(float alpha);
|
||||
|
||||
// Change the current transform matrix.
|
||||
void translate(float x, float y, float z);
|
||||
|
||||
void translate(float x, float y);
|
||||
|
||||
void scale(float sx, float sy, float sz);
|
||||
|
||||
void rotate(float angle, float x, float y, float z);
|
||||
|
||||
void multiplyMatrix(float[] mMatrix, int offset);
|
||||
|
||||
// Pushes the configuration state (matrix, and alpha) onto
|
||||
// a private stack.
|
||||
void save();
|
||||
|
||||
// Same as save(), but only save those specified in saveFlags.
|
||||
void save(int saveFlags);
|
||||
|
||||
// Pops from the top of the stack as current configuration state (matrix,
|
||||
// alpha, and clip). This call balances a previous call to save(), and is
|
||||
// used to remove all modifications to the configuration state since the
|
||||
// last save call.
|
||||
void restore();
|
||||
|
||||
// Draws a line using the specified paint from (x1, y1) to (x2, y2).
|
||||
// (Both end points are included).
|
||||
void drawLine(float x1, float y1, float x2, float y2, GLPaint paint);
|
||||
|
||||
// Draws a rectangle using the specified paint from (x1, y1) to (x2, y2).
|
||||
// (Both end points are included).
|
||||
void drawRect(float x1, float y1, float x2, float y2, GLPaint paint);
|
||||
|
||||
// Draws a oval using the specified paint for (cx, cy, radiusX, radiusY)
|
||||
// (Both end points are included).
|
||||
void drawOval(float cx, float cy, float radiusX, float radiusY,
|
||||
GLPaint paint);
|
||||
|
||||
// Draw a arc inside a rect
|
||||
void drawArc(float cx, float cy, float radiusX, float radiusY,
|
||||
float sweepAngle, GLPaint paint);
|
||||
|
||||
// Fills the specified rectangle with the specified color.
|
||||
void fillRect(float x, float y, float width, float height, int color);
|
||||
|
||||
// Fills the specified oval with the specified color.
|
||||
void fillOval(float cx, float cy, float radiusX, float radiusY, int color);
|
||||
|
||||
// Fills the specified sector with the specified color.
|
||||
void fillSector(float cx, float cy, float radiusX, float radiusY, float sweepAngle, int color);
|
||||
|
||||
// Draws a texture to the specified rectangle.
|
||||
void drawTexture(BasicTexture texture, int x, int y, int width, int height);
|
||||
|
||||
void drawMesh(BasicTexture tex, int x, int y, int xyBuffer, int uvBuffer, int indexBuffer, int indexCount);
|
||||
|
||||
// Draws the source rectangle part of the texture to the target rectangle.
|
||||
void drawTexture(BasicTexture texture, RectF source, RectF target);
|
||||
|
||||
// Draw a texture with a specified texture transform.
|
||||
void drawTexture(BasicTexture texture, float[] mTextureTransform, int x, int y, int w, int h);
|
||||
|
||||
// Draw two textures to the specified rectangle. The actual texture used is
|
||||
// from * (1 - ratio) + to * ratio
|
||||
// The two textures must have the same size.
|
||||
void drawMixed(BasicTexture from, int toColor, float ratio, int x, int y, int w, int h);
|
||||
|
||||
// Draw a region of a texture and a specified color to the specified
|
||||
// rectangle. The actual color used is from * (1 - ratio) + to * ratio.
|
||||
// The region of the texture is defined by parameter "src". The target
|
||||
// rectangle is specified by parameter "target".
|
||||
void drawMixed(BasicTexture from, int toColor, float ratio, RectF src, RectF target);
|
||||
|
||||
// Unloads the specified texture from the canvas. The resource allocated
|
||||
// to draw the texture will be released. The specified texture will return
|
||||
// to the unloaded state. This function should be called only from
|
||||
// BasicTexture or its descendant
|
||||
boolean unloadTexture(BasicTexture texture);
|
||||
|
||||
// Delete the specified buffer object, similar to unloadTexture.
|
||||
void deleteBuffer(int bufferId);
|
||||
|
||||
// Delete the textures and buffers in GL side. This function should only be
|
||||
// called in the GL thread.
|
||||
void deleteRecycledResources();
|
||||
|
||||
// Dump statistics information and clear the counters. For debug only.
|
||||
void dumpStatisticsAndClear();
|
||||
|
||||
void beginRenderTarget(RawTexture texture);
|
||||
|
||||
void endRenderTarget();
|
||||
|
||||
/**
|
||||
* Sets texture parameters to use GL_CLAMP_TO_EDGE for both
|
||||
* GL_TEXTURE_WRAP_S and GL_TEXTURE_WRAP_T. Sets texture parameters to be
|
||||
* GL_LINEAR for GL_TEXTURE_MIN_FILTER and GL_TEXTURE_MAG_FILTER.
|
||||
* bindTexture() must be called prior to this.
|
||||
*
|
||||
* @param texture The texture to set parameters on.
|
||||
*/
|
||||
void setTextureParameters(BasicTexture texture);
|
||||
|
||||
/**
|
||||
* Initializes the texture to a size by calling texImage2D on it.
|
||||
*
|
||||
* @param texture The texture to initialize the size.
|
||||
* @param format The texture format (e.g. GL_RGBA)
|
||||
* @param type The texture type (e.g. GL_UNSIGNED_BYTE)
|
||||
*/
|
||||
void initializeTextureSize(BasicTexture texture, int format, int type);
|
||||
|
||||
/**
|
||||
* Initializes the texture to a size by calling texImage2D on it.
|
||||
*
|
||||
* @param texture The texture to initialize the size.
|
||||
* @param bitmap The bitmap to initialize the bitmap with.
|
||||
*/
|
||||
void initializeTexture(BasicTexture texture, Bitmap bitmap);
|
||||
|
||||
/**
|
||||
* Calls glTexSubImage2D to upload a bitmap to the texture.
|
||||
*
|
||||
* @param texture The target texture to write to.
|
||||
* @param xOffset Specifies a texel offset in the x direction within the
|
||||
* texture array.
|
||||
* @param yOffset Specifies a texel offset in the y direction within the
|
||||
* texture array.
|
||||
* @param format The texture format (e.g. GL_RGBA)
|
||||
* @param type The texture type (e.g. GL_UNSIGNED_BYTE)
|
||||
*/
|
||||
void texSubImage2D(BasicTexture texture, int xOffset, int yOffset, Bitmap bitmap, int format, int type);
|
||||
|
||||
/**
|
||||
* Generates buffers and uploads the buffer data.
|
||||
*
|
||||
* @param buffer The buffer to upload
|
||||
* @return The buffer ID that was generated.
|
||||
*/
|
||||
int uploadBuffer(java.nio.FloatBuffer buffer);
|
||||
|
||||
/**
|
||||
* Generates buffers and uploads the element array buffer data.
|
||||
*
|
||||
* @param buffer The buffer to upload
|
||||
* @return The buffer ID that was generated.
|
||||
*/
|
||||
int uploadBuffer(java.nio.ByteBuffer buffer);
|
||||
|
||||
/**
|
||||
* After LightCycle makes GL calls, this method is called to restore the GL
|
||||
* configuration to the one expected by GLCanvas.
|
||||
*/
|
||||
void recoverFromLightCycle();
|
||||
|
||||
/**
|
||||
* Gets the bounds given by x, y, width, and height as well as the internal
|
||||
* matrix state. There is no special handling for non-90-degree rotations.
|
||||
* It only considers the lower-left and upper-right corners as the bounds.
|
||||
*
|
||||
* @param bounds The output bounds to write to.
|
||||
* @param x The left side of the input rectangle.
|
||||
* @param y The bottom of the input rectangle.
|
||||
* @param width The width of the input rectangle.
|
||||
* @param height The height of the input rectangle.
|
||||
*/
|
||||
void getBounds(Rect bounds, int x, int y, int width, int height);
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.hippo.glview.glrenderer;
|
||||
|
||||
import javax.microedition.khronos.opengles.GL11;
|
||||
import javax.microedition.khronos.opengles.GL11ExtensionPack;
|
||||
|
||||
/**
|
||||
* Open GL ES 1.1 implementation for generating and destroying texture IDs and
|
||||
* buffer IDs
|
||||
*/
|
||||
public class GLES11IdImpl implements GLId {
|
||||
|
||||
// Mutex for sNextId
|
||||
private final static Object sLock = new Object();
|
||||
private static int sNextId = 1;
|
||||
|
||||
@Override
|
||||
public int generateTexture() {
|
||||
synchronized (sLock) {
|
||||
return sNextId++;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glGenBuffers(int n, int[] buffers, int offset) {
|
||||
synchronized (sLock) {
|
||||
while (n-- > 0) {
|
||||
buffers[offset + n] = sNextId++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteTextures(GL11 gl, int n, int[] textures, int offset) {
|
||||
synchronized (sLock) {
|
||||
gl.glDeleteTextures(n, textures, offset);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteBuffers(GL11 gl, int n, int[] buffers, int offset) {
|
||||
synchronized (sLock) {
|
||||
gl.glDeleteBuffers(n, buffers, offset);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteFramebuffers(GL11ExtensionPack gl11ep, int n, int[] buffers, int offset) {
|
||||
synchronized (sLock) {
|
||||
gl11ep.glDeleteFramebuffersOES(n, buffers, offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 Tarsin Norbin
|
||||
*
|
||||
* This file is part of EhViewer
|
||||
*
|
||||
* EhViewer is free software: you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* EhViewer is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with EhViewer.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.glrenderer;
|
||||
|
||||
import android.opengl.GLES20;
|
||||
|
||||
import javax.microedition.khronos.opengles.GL11;
|
||||
import javax.microedition.khronos.opengles.GL11ExtensionPack;
|
||||
|
||||
public class GLES20IdImpl implements GLId {
|
||||
private final int[] mTempIntArray = new int[1];
|
||||
|
||||
@Override
|
||||
public int generateTexture() {
|
||||
GLES20.glGenTextures(1, mTempIntArray, 0);
|
||||
GLES20Canvas.checkError();
|
||||
return mTempIntArray[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glGenBuffers(int n, int[] buffers, int offset) {
|
||||
GLES20.glGenBuffers(n, buffers, offset);
|
||||
GLES20Canvas.checkError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteTextures(GL11 gl, int n, int[] textures, int offset) {
|
||||
GLES20.glDeleteTextures(n, textures, offset);
|
||||
GLES20Canvas.checkError();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void glDeleteBuffers(GL11 gl, int n, int[] buffers, int offset) {
|
||||
GLES20.glDeleteBuffers(n, buffers, offset);
|
||||
GLES20Canvas.checkError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteFramebuffers(GL11ExtensionPack gl11ep, int n, int[] buffers, int offset) {
|
||||
GLES20.glDeleteFramebuffers(n, buffers, offset);
|
||||
GLES20Canvas.checkError();
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.glrenderer;
|
||||
|
||||
import javax.microedition.khronos.opengles.GL11;
|
||||
import javax.microedition.khronos.opengles.GL11ExtensionPack;
|
||||
|
||||
// This mimics corresponding GL functions.
|
||||
public interface GLId {
|
||||
int generateTexture();
|
||||
|
||||
void glGenBuffers(int n, int[] buffers, int offset);
|
||||
|
||||
void glDeleteTextures(GL11 gl, int n, int[] textures, int offset);
|
||||
|
||||
void glDeleteBuffers(GL11 gl, int n, int[] buffers, int offset);
|
||||
|
||||
void glDeleteFramebuffers(GL11ExtensionPack gl11ep, int n, int[] buffers, int offset);
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.glrenderer;
|
||||
|
||||
public class GLPaint {
|
||||
private float mLineWidth = 1f;
|
||||
private int mColor = 0;
|
||||
private int mBackgroundColor = 0;
|
||||
|
||||
public int getColor() {
|
||||
return mColor;
|
||||
}
|
||||
|
||||
public void setColor(int color) {
|
||||
mColor = color;
|
||||
}
|
||||
|
||||
public int getBackgroundColor() {
|
||||
return mBackgroundColor;
|
||||
}
|
||||
|
||||
public void setBackgroundColor(int backgroundColor) {
|
||||
mBackgroundColor = backgroundColor;
|
||||
}
|
||||
|
||||
public float getLineWidth() {
|
||||
return mLineWidth;
|
||||
}
|
||||
|
||||
public void setLineWidth(float width) {
|
||||
if (width < 0) {
|
||||
throw new IllegalArgumentException("width < 0");
|
||||
}
|
||||
mLineWidth = width;
|
||||
}
|
||||
}
|
||||
@@ -1,202 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 Tarsin Norbin
|
||||
*
|
||||
* This file is part of EhViewer
|
||||
*
|
||||
* EhViewer is free software: you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* EhViewer is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with EhViewer.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.glrenderer;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Typeface;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
// TODO support multiline
|
||||
|
||||
/**
|
||||
* Works like movable type.<br>
|
||||
* <br>
|
||||
* Only support single line now
|
||||
*/
|
||||
public final class MovableTextTexture extends SpriteTexture {
|
||||
|
||||
private final char[] mCharacters;
|
||||
private final float[] mWidths;
|
||||
private final float mHeight;
|
||||
private final float mMaxWidth;
|
||||
|
||||
private MovableTextTexture(Bitmap bitmap, int count, int[] rects, char[] characters,
|
||||
float[] widths, float height, float maxWidth) {
|
||||
super(bitmap, false, count, rects);
|
||||
mCharacters = characters;
|
||||
mWidths = widths;
|
||||
mHeight = height;
|
||||
mMaxWidth = maxWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a TextTexture to draw text
|
||||
*
|
||||
* @param typeface the typeface
|
||||
* @param size text size
|
||||
* @param characters all Characters
|
||||
* @return the TextTexture
|
||||
*/
|
||||
public static MovableTextTexture create(Typeface typeface, int size, int color, char[] characters) {
|
||||
Paint paint = new Paint();
|
||||
paint.setAntiAlias(true);
|
||||
paint.setTextSize(size);
|
||||
paint.setColor(color);
|
||||
paint.setTypeface(typeface);
|
||||
|
||||
Paint.FontMetricsInt fmi = paint.getFontMetricsInt();
|
||||
int fixed = fmi.bottom;
|
||||
int height = fmi.bottom - fmi.top;
|
||||
|
||||
int length = characters.length;
|
||||
float[] widths = new float[length];
|
||||
paint.getTextWidths(characters, 0, length, widths);
|
||||
|
||||
// Calculate bitmap size
|
||||
float maxWidth = 0.0f;
|
||||
for (float f : widths) {
|
||||
maxWidth = Math.max(maxWidth, f);
|
||||
}
|
||||
int hCount = (int) Math.ceil(Math.sqrt(height / maxWidth * length));
|
||||
int vCount = (int) Math.ceil(Math.sqrt(maxWidth / height * length));
|
||||
if (hCount * (vCount - 1) > length) {
|
||||
vCount--;
|
||||
}
|
||||
if ((hCount - 1) * vCount > length) {
|
||||
hCount--;
|
||||
}
|
||||
|
||||
Bitmap bitmap = Bitmap.createBitmap((int) Math.ceil(hCount * maxWidth),
|
||||
(int) Math.ceil(vCount * height), Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
canvas.translate(0, height - fixed);
|
||||
|
||||
// Draw
|
||||
int[] rects = new int[length * 4];
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
for (int i = 0; i < length; i++) {
|
||||
int offset = i * 4;
|
||||
rects[offset] = x;
|
||||
rects[offset + 1] = y;
|
||||
rects[offset + 2] = (int) widths[i];
|
||||
rects[offset + 3] = height;
|
||||
|
||||
canvas.drawText(characters, i, 1, x, y, paint);
|
||||
|
||||
if (i % hCount == hCount - 1) {
|
||||
// The end of row
|
||||
x = 0;
|
||||
y += height;
|
||||
} else {
|
||||
x += maxWidth;
|
||||
}
|
||||
}
|
||||
|
||||
return new MovableTextTexture(bitmap, length, rects, characters, widths, height, maxWidth);
|
||||
}
|
||||
|
||||
public int[] getTextIndexes(String text) {
|
||||
char[] characters = mCharacters;
|
||||
|
||||
int length = text.length();
|
||||
int[] indexes = new int[length];
|
||||
for (int i = 0; i < length; i++) {
|
||||
char ch = text.charAt(i);
|
||||
indexes[i] = Arrays.binarySearch(characters, ch);
|
||||
}
|
||||
|
||||
return indexes;
|
||||
}
|
||||
|
||||
public float getTextWidth(String text) {
|
||||
char[] characters = mCharacters;
|
||||
float[] widths = mWidths;
|
||||
float width = 0.0f;
|
||||
|
||||
for (int i = 0, n = text.length(); i < n; i++) {
|
||||
char ch = text.charAt(i);
|
||||
int index = Arrays.binarySearch(characters, ch);
|
||||
if (index >= 0) {
|
||||
width += widths[index];
|
||||
} else {
|
||||
width += mMaxWidth;
|
||||
}
|
||||
}
|
||||
|
||||
return width;
|
||||
}
|
||||
|
||||
public float getTextWidth(int[] indexes) {
|
||||
float[] widths = mWidths;
|
||||
float width = 0.0f;
|
||||
int length = indexes.length;
|
||||
for (int i = 0; i < length; i++) {
|
||||
int index = indexes[i];
|
||||
if (index >= 0) {
|
||||
width += widths[index];
|
||||
} else {
|
||||
width += mMaxWidth;
|
||||
}
|
||||
}
|
||||
|
||||
return width;
|
||||
}
|
||||
|
||||
public float getMaxWidth() {
|
||||
return mMaxWidth;
|
||||
}
|
||||
|
||||
public float getTextHeight() {
|
||||
return mHeight;
|
||||
}
|
||||
|
||||
public void drawText(GLCanvas canvas, String text, int x, int y) {
|
||||
char[] characters = mCharacters;
|
||||
float[] widths = mWidths;
|
||||
|
||||
for (int i = 0, n = text.length(); i < n; i++) {
|
||||
char ch = text.charAt(i);
|
||||
int index = Arrays.binarySearch(characters, ch);
|
||||
if (index >= 0) {
|
||||
drawSprite(canvas, index, x, y);
|
||||
x += widths[index];
|
||||
} else {
|
||||
x += mMaxWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void drawText(GLCanvas canvas, int[] indexes, int x, int y) {
|
||||
float[] widths = mWidths;
|
||||
|
||||
for (int i = 0, n = indexes.length; i < n; i++) {
|
||||
int index = indexes[i];
|
||||
if (index >= 0) {
|
||||
drawSprite(canvas, index, x, y);
|
||||
x += widths[index];
|
||||
} else {
|
||||
x += mMaxWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.glrenderer;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.text.Layout;
|
||||
import android.text.StaticLayout;
|
||||
import android.text.TextPaint;
|
||||
|
||||
|
||||
// MultiLineTexture is a texture shows the content of a specified String.
|
||||
//
|
||||
// To create a MultiLineTexture, use the newInstance() method and specify
|
||||
// the String, the font size, and the color.
|
||||
class MultiLineTexture extends CanvasTexture {
|
||||
private final Layout mLayout;
|
||||
|
||||
private MultiLineTexture(Layout layout) {
|
||||
super(layout.getWidth(), layout.getHeight());
|
||||
mLayout = layout;
|
||||
}
|
||||
|
||||
public static MultiLineTexture newInstance(
|
||||
String text, int maxWidth, float textSize, int color,
|
||||
Layout.Alignment alignment) {
|
||||
TextPaint paint = StringTexture.getDefaultPaint(textSize, color);
|
||||
Layout layout = new StaticLayout(text, 0, text.length(), paint,
|
||||
maxWidth, alignment, 1, 0, true, null, 0);
|
||||
|
||||
return new MultiLineTexture(layout);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas, Bitmap backing) {
|
||||
mLayout.draw(canvas);
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.glrenderer;
|
||||
|
||||
import android.opengl.GLES20;
|
||||
import android.util.Log;
|
||||
|
||||
import javax.microedition.khronos.opengles.GL11;
|
||||
|
||||
public abstract class NativeTexture extends BasicTexture {
|
||||
|
||||
private static final String TAG = NativeTexture.class.getSimpleName();
|
||||
|
||||
private boolean mContentValid = true;
|
||||
|
||||
private boolean mOpaque = true;
|
||||
|
||||
public static void checkError() {
|
||||
int error = GLES20.glGetError();
|
||||
if (error != 0) {
|
||||
Throwable t = new Throwable();
|
||||
Log.e(TAG, "GL error: " + error, t);
|
||||
}
|
||||
}
|
||||
|
||||
public void invalidateContent() {
|
||||
mContentValid = false;
|
||||
}
|
||||
|
||||
protected abstract void texImage(boolean init);
|
||||
|
||||
private void uploadToCanvas(GLCanvas canvas) {
|
||||
// Get id
|
||||
mId = canvas.getGLId().generateTexture();
|
||||
|
||||
// Prepare
|
||||
canvas.setTextureParameters(this);
|
||||
|
||||
// Call glTexImage2D
|
||||
GLES20.glBindTexture(getTarget(), mId);
|
||||
checkError();
|
||||
texImage(true);
|
||||
checkError();
|
||||
|
||||
setAssociatedCanvas(canvas);
|
||||
mState = STATE_LOADED;
|
||||
mContentValid = true;
|
||||
}
|
||||
|
||||
public void updateContent(GLCanvas canvas) {
|
||||
if (!isLoaded()) {
|
||||
uploadToCanvas(canvas);
|
||||
} else if (!mContentValid) {
|
||||
// Call glTexSubImage2D
|
||||
GLES20.glBindTexture(getTarget(), mId);
|
||||
checkError();
|
||||
texImage(false);
|
||||
checkError();
|
||||
mContentValid = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the content on GPU is valid.
|
||||
*/
|
||||
public boolean isContentValid() {
|
||||
return isLoaded() && mContentValid;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onBind(GLCanvas canvas) {
|
||||
updateContent(canvas);
|
||||
return isContentValid();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getTarget() {
|
||||
return GL11.GL_TEXTURE_2D;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpaque() {
|
||||
return mOpaque;
|
||||
}
|
||||
|
||||
public void setOpaque(boolean isOpaque) {
|
||||
mOpaque = isOpaque;
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.glrenderer;
|
||||
|
||||
import android.graphics.Rect;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
// See "frameworks/base/include/utils/ResourceTypes.h" for the format of
|
||||
// NinePatch chunk.
|
||||
class NinePatchChunk {
|
||||
|
||||
public static final int NO_COLOR = 0x00000001;
|
||||
public static final int TRANSPARENT_COLOR = 0x00000000;
|
||||
|
||||
public Rect mPaddings = new Rect();
|
||||
|
||||
public int[] mDivX;
|
||||
public int[] mDivY;
|
||||
public int[] mColor;
|
||||
|
||||
private static void readIntArray(int[] data, ByteBuffer buffer) {
|
||||
for (int i = 0, n = data.length; i < n; ++i) {
|
||||
data[i] = buffer.getInt();
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkDivCount(int length) {
|
||||
if (length == 0 || (length & 0x01) != 0) {
|
||||
throw new RuntimeException("invalid nine-patch: " + length);
|
||||
}
|
||||
}
|
||||
|
||||
public static NinePatchChunk deserialize(byte[] data) {
|
||||
ByteBuffer byteBuffer =
|
||||
ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
|
||||
|
||||
byte wasSerialized = byteBuffer.get();
|
||||
if (wasSerialized == 0) return null;
|
||||
|
||||
NinePatchChunk chunk = new NinePatchChunk();
|
||||
chunk.mDivX = new int[byteBuffer.get()];
|
||||
chunk.mDivY = new int[byteBuffer.get()];
|
||||
chunk.mColor = new int[byteBuffer.get()];
|
||||
|
||||
checkDivCount(chunk.mDivX.length);
|
||||
checkDivCount(chunk.mDivY.length);
|
||||
|
||||
// skip 8 bytes
|
||||
byteBuffer.getInt();
|
||||
byteBuffer.getInt();
|
||||
|
||||
chunk.mPaddings.left = byteBuffer.getInt();
|
||||
chunk.mPaddings.right = byteBuffer.getInt();
|
||||
chunk.mPaddings.top = byteBuffer.getInt();
|
||||
chunk.mPaddings.bottom = byteBuffer.getInt();
|
||||
|
||||
// skip 4 bytes
|
||||
byteBuffer.getInt();
|
||||
|
||||
readIntArray(chunk.mDivX, byteBuffer);
|
||||
readIntArray(chunk.mDivY, byteBuffer);
|
||||
readIntArray(chunk.mColor, byteBuffer);
|
||||
|
||||
return chunk;
|
||||
}
|
||||
}
|
||||
@@ -1,424 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.glrenderer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Rect;
|
||||
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
// NinePatchTexture is a texture backed by a NinePatch resource.
|
||||
//
|
||||
// getPaddings() returns paddings specified in the NinePatch.
|
||||
// getNinePatchChunk() returns the layout data specified in the NinePatch.
|
||||
//
|
||||
public class NinePatchTexture extends ResourceTexture {
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = "NinePatchTexture";
|
||||
private final SmallCache<NinePatchInstance> mInstanceCache
|
||||
= new SmallCache<NinePatchInstance>();
|
||||
private NinePatchChunk mChunk;
|
||||
|
||||
public NinePatchTexture(Context context, int resId) {
|
||||
super(context, resId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bitmap onGetBitmap() {
|
||||
if (mBitmap != null) return mBitmap;
|
||||
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
|
||||
Bitmap bitmap = BitmapFactory.decodeResource(
|
||||
mContext.getResources(), mResId, options);
|
||||
mBitmap = bitmap;
|
||||
setSize(bitmap.getWidth(), bitmap.getHeight());
|
||||
byte[] chunkData = bitmap.getNinePatchChunk();
|
||||
mChunk = chunkData == null
|
||||
? null
|
||||
: NinePatchChunk.deserialize(bitmap.getNinePatchChunk());
|
||||
if (mChunk == null) {
|
||||
throw new RuntimeException("invalid nine-patch image: " + mResId);
|
||||
}
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
public Rect getPaddings() {
|
||||
// get the paddings from nine patch
|
||||
if (mChunk == null) onGetBitmap();
|
||||
return mChunk.mPaddings;
|
||||
}
|
||||
|
||||
public NinePatchChunk getNinePatchChunk() {
|
||||
if (mChunk == null) onGetBitmap();
|
||||
return mChunk;
|
||||
}
|
||||
|
||||
private NinePatchInstance findInstance(GLCanvas canvas, int w, int h) {
|
||||
int key = w;
|
||||
key = (key << 16) | h;
|
||||
NinePatchInstance instance = mInstanceCache.get(key);
|
||||
|
||||
if (instance == null) {
|
||||
instance = new NinePatchInstance(this, w, h);
|
||||
NinePatchInstance removed = mInstanceCache.put(key, instance);
|
||||
if (removed != null) {
|
||||
removed.recycle(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, int x, int y, int w, int h) {
|
||||
if (!isLoaded()) {
|
||||
mInstanceCache.clear();
|
||||
}
|
||||
|
||||
if (w != 0 && h != 0) {
|
||||
findInstance(canvas, w, h).draw(canvas, this, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recycle() {
|
||||
super.recycle();
|
||||
GLCanvas canvas = mCanvasRef;
|
||||
if (canvas == null) return;
|
||||
int n = mInstanceCache.size();
|
||||
for (int i = 0; i < n; i++) {
|
||||
NinePatchInstance instance = mInstanceCache.valueAt(i);
|
||||
instance.recycle(canvas);
|
||||
}
|
||||
mInstanceCache.clear();
|
||||
}
|
||||
|
||||
// This is a simple cache for a small number of things. Linear search
|
||||
// is used because the cache is small. It also tries to remove less used
|
||||
// item when the cache is full by moving the often-used items to the front.
|
||||
private static class SmallCache<V> {
|
||||
private static final int CACHE_SIZE = 16;
|
||||
private static final int CACHE_SIZE_START_MOVE = CACHE_SIZE / 2;
|
||||
private final int[] mKey = new int[CACHE_SIZE];
|
||||
private final V[] mValue = (V[]) new Object[CACHE_SIZE];
|
||||
private int mCount; // number of items in this cache
|
||||
|
||||
// Puts a value into the cache. If the cache is full, also returns
|
||||
// a less used item, otherwise returns null.
|
||||
public V put(int key, V value) {
|
||||
if (mCount == CACHE_SIZE) {
|
||||
V old = mValue[CACHE_SIZE - 1]; // remove the last item
|
||||
mKey[CACHE_SIZE - 1] = key;
|
||||
mValue[CACHE_SIZE - 1] = value;
|
||||
return old;
|
||||
} else {
|
||||
mKey[mCount] = key;
|
||||
mValue[mCount] = value;
|
||||
mCount++;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public V get(int key) {
|
||||
for (int i = 0; i < mCount; i++) {
|
||||
if (mKey[i] == key) {
|
||||
// Move the accessed item one position to the front, so it
|
||||
// will less likely to be removed when cache is full. Only
|
||||
// do this if the cache is starting to get full.
|
||||
if (mCount > CACHE_SIZE_START_MOVE && i > 0) {
|
||||
int tmpKey = mKey[i];
|
||||
mKey[i] = mKey[i - 1];
|
||||
mKey[i - 1] = tmpKey;
|
||||
|
||||
V tmpValue = mValue[i];
|
||||
mValue[i] = mValue[i - 1];
|
||||
mValue[i - 1] = tmpValue;
|
||||
}
|
||||
return mValue[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
for (int i = 0; i < mCount; i++) {
|
||||
mValue[i] = null; // make sure it's can be garbage-collected.
|
||||
}
|
||||
mCount = 0;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return mCount;
|
||||
}
|
||||
|
||||
public V valueAt(int i) {
|
||||
return mValue[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This keeps data for a specialization of NinePatchTexture with the size
|
||||
// (width, height). We pre-compute the coordinates for efficiency.
|
||||
class NinePatchInstance {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = "NinePatchInstance";
|
||||
|
||||
// We need 16 vertices for a normal nine-patch image (the 4x4 vertices)
|
||||
private static final int VERTEX_BUFFER_SIZE = 16 * 2;
|
||||
|
||||
// We need 22 indices for a normal nine-patch image, plus 2 for each
|
||||
// transparent region. Current there are at most 1 transparent region.
|
||||
private static final int INDEX_BUFFER_SIZE = 22 + 2;
|
||||
|
||||
private FloatBuffer mXyBuffer;
|
||||
private FloatBuffer mUvBuffer;
|
||||
private ByteBuffer mIndexBuffer;
|
||||
|
||||
// Names for buffer names: xy, uv, index.
|
||||
private int mXyBufferName = -1;
|
||||
private int mUvBufferName;
|
||||
private int mIndexBufferName;
|
||||
|
||||
private int mIdxCount;
|
||||
|
||||
public NinePatchInstance(NinePatchTexture tex, int width, int height) {
|
||||
NinePatchChunk chunk = tex.getNinePatchChunk();
|
||||
|
||||
if (width <= 0 || height <= 0) {
|
||||
throw new RuntimeException("invalid dimension");
|
||||
}
|
||||
|
||||
// The code should be easily extended to handle the general cases by
|
||||
// allocating more space for buffers. But let's just handle the only
|
||||
// use case.
|
||||
if (chunk.mDivX.length != 2 || chunk.mDivY.length != 2) {
|
||||
throw new RuntimeException("unsupported nine patch");
|
||||
}
|
||||
|
||||
float[] divX = new float[4];
|
||||
float[] divY = new float[4];
|
||||
float[] divU = new float[4];
|
||||
float[] divV = new float[4];
|
||||
|
||||
int nx = stretch(divX, divU, chunk.mDivX, tex.getWidth(), width);
|
||||
int ny = stretch(divY, divV, chunk.mDivY, tex.getHeight(), height);
|
||||
|
||||
prepareVertexData(divX, divY, divU, divV, nx, ny, chunk.mColor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stretches the texture according to the nine-patch rules. It will
|
||||
* linearly distribute the strechy parts defined in the nine-patch chunk to
|
||||
* the target area.
|
||||
*
|
||||
* <pre>
|
||||
* source
|
||||
* /--------------^---------------\
|
||||
* u0 u1 u2 u3 u4 u5
|
||||
* div ---> |fffff|ssssssss|fff|ssssss|ffff| ---> u
|
||||
* | div0 div1 div2 div3 |
|
||||
* | | / / / /
|
||||
* | | / / / /
|
||||
* | | / / / /
|
||||
* |fffff|ssss|fff|sss|ffff| ---> x
|
||||
* x0 x1 x2 x3 x4 x5
|
||||
* \----------v------------/
|
||||
* target
|
||||
*
|
||||
* f: fixed segment
|
||||
* s: stretchy segment
|
||||
* </pre>
|
||||
*
|
||||
* @param div the stretch parts defined in nine-patch chunk
|
||||
* @param source the length of the texture
|
||||
* @param target the length on the drawing plan
|
||||
* @param u output, the positions of these dividers in the texture
|
||||
* coordinate
|
||||
* @param x output, the corresponding position of these dividers on the
|
||||
* drawing plan
|
||||
* @return the number of these dividers.
|
||||
*/
|
||||
private static int stretch(
|
||||
float[] x, float[] u, int[] div, int source, int target) {
|
||||
int textureSize = MathUtils.nextPowerOf2(source);
|
||||
float textureBound = (float) source / textureSize;
|
||||
|
||||
float stretch = 0;
|
||||
for (int i = 0, n = div.length; i < n; i += 2) {
|
||||
stretch += div[i + 1] - div[i];
|
||||
}
|
||||
|
||||
float remaining = target - source + stretch;
|
||||
|
||||
float lastX = 0;
|
||||
float lastU = 0;
|
||||
|
||||
x[0] = 0;
|
||||
u[0] = 0;
|
||||
for (int i = 0, n = div.length; i < n; i += 2) {
|
||||
// Make the stretchy segment a little smaller to prevent sampling
|
||||
// on neighboring fixed segments.
|
||||
// fixed segment
|
||||
x[i + 1] = lastX + (div[i] - lastU) + 0.5f;
|
||||
u[i + 1] = Math.min((div[i] + 0.5f) / textureSize, textureBound);
|
||||
|
||||
// stretchy segment
|
||||
float partU = div[i + 1] - div[i];
|
||||
float partX = remaining * partU / stretch;
|
||||
remaining -= partX;
|
||||
stretch -= partU;
|
||||
|
||||
lastX = x[i + 1] + partX;
|
||||
lastU = div[i + 1];
|
||||
x[i + 2] = lastX - 0.5f;
|
||||
u[i + 2] = Math.min((lastU - 0.5f) / textureSize, textureBound);
|
||||
}
|
||||
// the last fixed segment
|
||||
x[div.length + 1] = target;
|
||||
u[div.length + 1] = textureBound;
|
||||
|
||||
// remove segments with length 0.
|
||||
int last = 0;
|
||||
for (int i = 1, n = div.length + 2; i < n; ++i) {
|
||||
if ((x[i] - x[last]) < 1f) continue;
|
||||
x[++last] = x[i];
|
||||
u[last] = u[i];
|
||||
}
|
||||
return last + 1;
|
||||
}
|
||||
|
||||
private static ByteBuffer allocateDirectNativeOrderBuffer(int size) {
|
||||
return ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder());
|
||||
}
|
||||
|
||||
private void prepareVertexData(float[] x, float[] y, float[] u, float[] v,
|
||||
int nx, int ny, int[] color) {
|
||||
/*
|
||||
* Given a 3x3 nine-patch image, the vertex order is defined as the
|
||||
* following graph:
|
||||
*
|
||||
* (0) (1) (2) (3)
|
||||
* | /| /| /|
|
||||
* | / | / | / |
|
||||
* (4) (5) (6) (7)
|
||||
* | \ | \ | \ |
|
||||
* | \| \| \|
|
||||
* (8) (9) (A) (B)
|
||||
* | /| /| /|
|
||||
* | / | / | / |
|
||||
* (C) (D) (E) (F)
|
||||
*
|
||||
* And we draw the triangle strip in the following index order:
|
||||
*
|
||||
* index: 04152637B6A5948C9DAEBF
|
||||
*/
|
||||
int pntCount = 0;
|
||||
float[] xy = new float[VERTEX_BUFFER_SIZE];
|
||||
float[] uv = new float[VERTEX_BUFFER_SIZE];
|
||||
for (int j = 0; j < ny; ++j) {
|
||||
for (int i = 0; i < nx; ++i) {
|
||||
int xIndex = (pntCount++) << 1;
|
||||
int yIndex = xIndex + 1;
|
||||
xy[xIndex] = x[i];
|
||||
xy[yIndex] = y[j];
|
||||
uv[xIndex] = u[i];
|
||||
uv[yIndex] = v[j];
|
||||
}
|
||||
}
|
||||
|
||||
int idxCount = 1;
|
||||
boolean isForward = false;
|
||||
byte[] index = new byte[INDEX_BUFFER_SIZE];
|
||||
for (int row = 0; row < ny - 1; row++) {
|
||||
--idxCount;
|
||||
isForward = !isForward;
|
||||
|
||||
int start, end, inc;
|
||||
if (isForward) {
|
||||
start = 0;
|
||||
end = nx;
|
||||
inc = 1;
|
||||
} else {
|
||||
start = nx - 1;
|
||||
end = -1;
|
||||
inc = -1;
|
||||
}
|
||||
|
||||
for (int col = start; col != end; col += inc) {
|
||||
int k = row * nx + col;
|
||||
if (col != start) {
|
||||
int colorIdx = row * (nx - 1) + col;
|
||||
if (isForward) colorIdx--;
|
||||
if (color[colorIdx] == NinePatchChunk.TRANSPARENT_COLOR) {
|
||||
index[idxCount] = index[idxCount - 1];
|
||||
++idxCount;
|
||||
index[idxCount++] = (byte) k;
|
||||
}
|
||||
}
|
||||
|
||||
index[idxCount++] = (byte) k;
|
||||
index[idxCount++] = (byte) (k + nx);
|
||||
}
|
||||
}
|
||||
|
||||
mIdxCount = idxCount;
|
||||
|
||||
int size = (pntCount * 2) * (Float.SIZE / Byte.SIZE);
|
||||
mXyBuffer = allocateDirectNativeOrderBuffer(size).asFloatBuffer();
|
||||
mUvBuffer = allocateDirectNativeOrderBuffer(size).asFloatBuffer();
|
||||
mIndexBuffer = allocateDirectNativeOrderBuffer(mIdxCount);
|
||||
|
||||
mXyBuffer.put(xy, 0, pntCount * 2).position(0);
|
||||
mUvBuffer.put(uv, 0, pntCount * 2).position(0);
|
||||
mIndexBuffer.put(index, 0, idxCount).position(0);
|
||||
}
|
||||
|
||||
private void prepareBuffers(GLCanvas canvas) {
|
||||
mXyBufferName = canvas.uploadBuffer(mXyBuffer);
|
||||
mUvBufferName = canvas.uploadBuffer(mUvBuffer);
|
||||
mIndexBufferName = canvas.uploadBuffer(mIndexBuffer);
|
||||
|
||||
// These buffers are never used again.
|
||||
mXyBuffer = null;
|
||||
mUvBuffer = null;
|
||||
mIndexBuffer = null;
|
||||
}
|
||||
|
||||
public void draw(GLCanvas canvas, NinePatchTexture tex, int x, int y) {
|
||||
if (mXyBufferName == -1) {
|
||||
prepareBuffers(canvas);
|
||||
}
|
||||
canvas.drawMesh(tex, x, y, mXyBufferName, mUvBufferName, mIndexBufferName, mIdxCount);
|
||||
}
|
||||
|
||||
public void recycle(GLCanvas canvas) {
|
||||
if (mXyBuffer == null) {
|
||||
canvas.deleteBuffer(mXyBufferName);
|
||||
canvas.deleteBuffer(mUvBufferName);
|
||||
canvas.deleteBuffer(mIndexBufferName);
|
||||
mXyBufferName = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.glrenderer;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import javax.microedition.khronos.opengles.GL11;
|
||||
|
||||
public class RawTexture extends BasicTexture {
|
||||
private static final String TAG = "RawTexture";
|
||||
|
||||
private final boolean mOpaque;
|
||||
private boolean mIsFlipped;
|
||||
|
||||
public RawTexture(int width, int height, boolean opaque) {
|
||||
mOpaque = opaque;
|
||||
setSize(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpaque() {
|
||||
return mOpaque;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFlippedVertically() {
|
||||
return mIsFlipped;
|
||||
}
|
||||
|
||||
public void setIsFlippedVertically(boolean isFlipped) {
|
||||
mIsFlipped = isFlipped;
|
||||
}
|
||||
|
||||
protected void prepare(GLCanvas canvas) {
|
||||
GLId glId = canvas.getGLId();
|
||||
mId = glId.generateTexture();
|
||||
canvas.initializeTextureSize(this, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE);
|
||||
canvas.setTextureParameters(this);
|
||||
mState = STATE_LOADED;
|
||||
setAssociatedCanvas(canvas);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onBind(GLCanvas canvas) {
|
||||
if (isLoaded()) return true;
|
||||
Log.w(TAG, "lost the content due to context change");
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void yield() {
|
||||
// we cannot free the texture because we have no backup.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getTarget() {
|
||||
return GL11.GL_TEXTURE_2D;
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.glrenderer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
|
||||
// ResourceTexture is a texture whose Bitmap is decoded from a resource.
|
||||
// By default ResourceTexture is not opaque.
|
||||
public class ResourceTexture extends UploadedTexture {
|
||||
|
||||
protected final Context mContext;
|
||||
protected final int mResId;
|
||||
|
||||
public ResourceTexture(Context context, int resId) {
|
||||
if (context == null) {
|
||||
throw new IllegalArgumentException("context == null");
|
||||
}
|
||||
mContext = context;
|
||||
mResId = resId;
|
||||
setOpaque(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bitmap onGetBitmap() {
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
|
||||
return BitmapFactory.decodeResource(
|
||||
mContext.getResources(), mResId, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFreeBitmap(Bitmap bitmap) {
|
||||
if (!inFinalizer()) {
|
||||
bitmap.recycle();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 Tarsin Norbin
|
||||
*
|
||||
* This file is part of EhViewer
|
||||
*
|
||||
* EhViewer is free software: you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* EhViewer is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with EhViewer.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.glrenderer;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.RectF;
|
||||
|
||||
import com.hippo.yorozuya.AssertUtils;
|
||||
|
||||
public class SpriteTexture extends TiledTexture {
|
||||
|
||||
private final int mCount;
|
||||
private final int[] mRects;
|
||||
|
||||
private final RectF mTempSource = new RectF();
|
||||
private final RectF mTempTarget = new RectF();
|
||||
|
||||
public SpriteTexture(Bitmap bitmap, boolean isOpaque, int count, int[] rects) {
|
||||
super(bitmap, isOpaque);
|
||||
|
||||
AssertUtils.assertEquals("rects.length must be count * 4", count * 4, rects.length);
|
||||
mCount = count;
|
||||
mRects = rects;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return mCount;
|
||||
}
|
||||
|
||||
public void drawSprite(GLCanvas canvas, int index, int x, int y) {
|
||||
int[] rects = mRects;
|
||||
int offset = index * 4;
|
||||
int sourceX = rects[offset];
|
||||
int sourceY = rects[offset + 1];
|
||||
int sourceWidth = rects[offset + 2];
|
||||
int sourceHeight = rects[offset + 3];
|
||||
mTempSource.set(sourceX, sourceY, sourceX + sourceWidth, sourceY + sourceHeight);
|
||||
mTempTarget.set(x, y, x + sourceWidth, y + sourceHeight);
|
||||
draw(canvas, mTempSource, mTempTarget);
|
||||
}
|
||||
|
||||
public void drawSprite(GLCanvas canvas, int index, int x, int y, int width, int height) {
|
||||
int[] rects = mRects;
|
||||
int offset = index * 4;
|
||||
int sourceX = rects[offset];
|
||||
int sourceY = rects[offset + 1];
|
||||
mTempSource.set(sourceX, sourceY, sourceX + rects[offset + 2], sourceY + rects[offset + 3]);
|
||||
mTempTarget.set(x, y, x + width, y + height);
|
||||
draw(canvas, mTempSource, mTempTarget);
|
||||
}
|
||||
|
||||
public void drawSpriteMixed(GLCanvas canvas, int index, int color, float ratio, int x, int y) {
|
||||
int[] rects = mRects;
|
||||
int offset = index * 4;
|
||||
int sourceX = rects[offset];
|
||||
int sourceY = rects[offset + 1];
|
||||
int sourceWidth = rects[offset + 2];
|
||||
int sourceHeight = rects[offset + 3];
|
||||
mTempSource.set(sourceX, sourceY, sourceX + sourceWidth, sourceY + sourceHeight);
|
||||
mTempTarget.set(x, y, x + sourceWidth, y + sourceHeight);
|
||||
drawMixed(canvas, color, ratio, mTempSource, mTempTarget);
|
||||
}
|
||||
|
||||
public void drawSpriteMixed(GLCanvas canvas, int index, int color, float ratio,
|
||||
int x, int y, int width, int height) {
|
||||
int[] rects = mRects;
|
||||
int offset = index * 4;
|
||||
int sourceX = rects[offset];
|
||||
int sourceY = rects[offset + 1];
|
||||
mTempSource.set(sourceX, sourceY, sourceX + rects[offset + 2],
|
||||
sourceY + rects[offset + 3]);
|
||||
mTempTarget.set(x, y, x + width, y + height);
|
||||
drawMixed(canvas, color, ratio, mTempSource, mTempTarget);
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.glrenderer;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint.FontMetricsInt;
|
||||
import android.graphics.Typeface;
|
||||
import android.text.TextPaint;
|
||||
import android.text.TextUtils;
|
||||
|
||||
// StringTexture is a texture shows the content of a specified String.
|
||||
//
|
||||
// To create a StringTexture, use the newInstance() method and specify
|
||||
// the String, the font size, and the color.
|
||||
public class StringTexture extends CanvasTexture {
|
||||
private final String mText;
|
||||
private final TextPaint mPaint;
|
||||
private final FontMetricsInt mMetrics;
|
||||
|
||||
private StringTexture(String text, TextPaint paint,
|
||||
FontMetricsInt metrics, int width, int height) {
|
||||
super(width, height);
|
||||
mText = text;
|
||||
mPaint = paint;
|
||||
mMetrics = metrics;
|
||||
}
|
||||
|
||||
public static TextPaint getDefaultPaint(float textSize, int color) {
|
||||
TextPaint paint = new TextPaint();
|
||||
paint.setTextSize(textSize);
|
||||
paint.setAntiAlias(true);
|
||||
paint.setColor(color);
|
||||
paint.setShadowLayer(2f, 0f, 0f, Color.BLACK);
|
||||
return paint;
|
||||
}
|
||||
|
||||
public static StringTexture newInstance(
|
||||
String text, float textSize, int color) {
|
||||
return newInstance(text, getDefaultPaint(textSize, color));
|
||||
}
|
||||
|
||||
public static StringTexture newInstance(
|
||||
String text, float textSize, int color,
|
||||
float lengthLimit, boolean isBold) {
|
||||
TextPaint paint = getDefaultPaint(textSize, color);
|
||||
if (isBold) {
|
||||
paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
|
||||
}
|
||||
if (lengthLimit > 0) {
|
||||
text = TextUtils.ellipsize(
|
||||
text, paint, lengthLimit, TextUtils.TruncateAt.END).toString();
|
||||
}
|
||||
return newInstance(text, paint);
|
||||
}
|
||||
|
||||
private static StringTexture newInstance(String text, TextPaint paint) {
|
||||
FontMetricsInt metrics = paint.getFontMetricsInt();
|
||||
int width = (int) Math.ceil(paint.measureText(text));
|
||||
int height = metrics.bottom - metrics.top;
|
||||
// The texture size needs to be at least 1x1.
|
||||
if (width <= 0) width = 1;
|
||||
if (height <= 0) height = 1;
|
||||
return new StringTexture(text, paint, metrics, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas, Bitmap backing) {
|
||||
canvas.translate(0, -mMetrics.ascent);
|
||||
canvas.drawText(mText, 0, 0, mPaint);
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.glrenderer;
|
||||
|
||||
|
||||
import android.graphics.RectF;
|
||||
|
||||
// Texture is a rectangular image which can be drawn on GLCanvas.
|
||||
// The isOpaque() function gives a hint about whether the texture is opaque,
|
||||
// so the drawing can be done faster.
|
||||
//
|
||||
// This is the current texture hierarchy:
|
||||
//
|
||||
// Texture
|
||||
// -- ColorTexture
|
||||
// -- FadeInTexture
|
||||
// -- BasicTexture
|
||||
// -- UploadedTexture
|
||||
// -- BitmapTexture
|
||||
// -- Tile
|
||||
// -- ResourceTexture
|
||||
// -- NinePatchTexture
|
||||
// -- CanvasTexture
|
||||
// -- StringTexture
|
||||
//
|
||||
public interface Texture {
|
||||
int getWidth();
|
||||
|
||||
int getHeight();
|
||||
|
||||
void draw(GLCanvas canvas, int x, int y);
|
||||
|
||||
void draw(GLCanvas canvas, int x, int y, int w, int h);
|
||||
|
||||
void draw(GLCanvas canvas, RectF source, RectF target);
|
||||
|
||||
boolean isOpaque();
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.glrenderer;
|
||||
|
||||
import com.hippo.glview.view.GLRoot;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
|
||||
public class TextureUploader implements GLRoot.OnGLIdleListener {
|
||||
private static final int INIT_CAPACITY = 64;
|
||||
private static final int QUOTA_PER_FRAME = 1;
|
||||
|
||||
private final ArrayDeque<UploadedTexture> mFgTextures =
|
||||
new ArrayDeque<>(INIT_CAPACITY);
|
||||
private final ArrayDeque<UploadedTexture> mBgTextures =
|
||||
new ArrayDeque<>(INIT_CAPACITY);
|
||||
private final GLRoot mGLRoot;
|
||||
private volatile boolean mIsQueued = false;
|
||||
|
||||
public TextureUploader(GLRoot root) {
|
||||
mGLRoot = root;
|
||||
}
|
||||
|
||||
public synchronized void clear() {
|
||||
while (!mFgTextures.isEmpty()) {
|
||||
mFgTextures.pop().setIsUploading(false);
|
||||
}
|
||||
while (!mBgTextures.isEmpty()) {
|
||||
mBgTextures.pop().setIsUploading(false);
|
||||
}
|
||||
}
|
||||
|
||||
// caller should hold synchronized on "this"
|
||||
private void queueSelfIfNeed() {
|
||||
if (mIsQueued) return;
|
||||
mIsQueued = true;
|
||||
mGLRoot.addOnGLIdleListener(this);
|
||||
}
|
||||
|
||||
public synchronized void addBgTexture(UploadedTexture t) {
|
||||
if (t.isContentValid()) return;
|
||||
mBgTextures.addLast(t);
|
||||
t.setIsUploading(true);
|
||||
queueSelfIfNeed();
|
||||
}
|
||||
|
||||
public synchronized void addFgTexture(UploadedTexture t) {
|
||||
if (t.isContentValid()) return;
|
||||
mFgTextures.addLast(t);
|
||||
t.setIsUploading(true);
|
||||
queueSelfIfNeed();
|
||||
}
|
||||
|
||||
private int upload(GLCanvas canvas, ArrayDeque<UploadedTexture> deque,
|
||||
int uploadQuota, boolean isBackground) {
|
||||
while (uploadQuota > 0) {
|
||||
UploadedTexture t;
|
||||
synchronized (this) {
|
||||
if (deque.isEmpty()) break;
|
||||
t = deque.removeFirst();
|
||||
t.setIsUploading(false);
|
||||
if (t.isContentValid()) continue;
|
||||
|
||||
// this has to be protected by the synchronized block
|
||||
// to prevent the inner bitmap get recycled
|
||||
t.updateContent(canvas);
|
||||
}
|
||||
|
||||
// It will took some more time for a texture to be drawn for
|
||||
// the first time.
|
||||
// Thus, when scrolling, if a new column appears on screen,
|
||||
// it may cause a UI jank even these textures are uploaded.
|
||||
if (isBackground) t.draw(canvas, 0, 0);
|
||||
--uploadQuota;
|
||||
}
|
||||
return uploadQuota;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) {
|
||||
int uploadQuota = QUOTA_PER_FRAME;
|
||||
uploadQuota = upload(canvas, mFgTextures, uploadQuota, false);
|
||||
if (uploadQuota < QUOTA_PER_FRAME) mGLRoot.requestRender();
|
||||
upload(canvas, mBgTextures, uploadQuota, true);
|
||||
synchronized (this) {
|
||||
mIsQueued = !mFgTextures.isEmpty() || !mBgTextures.isEmpty();
|
||||
return mIsQueued;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,370 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.glrenderer;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.graphics.RectF;
|
||||
import android.os.SystemClock;
|
||||
|
||||
import com.hippo.glview.view.GLRoot;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
|
||||
// This class is similar to BitmapTexture, except the bitmap is
|
||||
// split into tiles. By doing so, we may increase the time required to
|
||||
// upload the whole bitmap but we reduce the time of uploading each tile
|
||||
// so it make the animation more smooth and prevents jank.
|
||||
public class TiledTexture implements Texture {
|
||||
private static final int CONTENT_SIZE = 254;
|
||||
private static final int BORDER_SIZE = 1;
|
||||
private static final int TILE_SIZE = CONTENT_SIZE + 2 * BORDER_SIZE;
|
||||
private static final int INIT_CAPACITY = 8;
|
||||
|
||||
// We are targeting at 60fps, so we have 16ms for each frame.
|
||||
// In this 16ms, we use about 4~8 ms to upload tiles.
|
||||
private static final long UPLOAD_TILE_LIMIT = 4; // ms
|
||||
private static final Object sFreeTileLock = new Object();
|
||||
private static final Bitmap sUploadBitmap;
|
||||
private static final Canvas sCanvas;
|
||||
private static final Paint sBitmapPaint;
|
||||
private static final Paint sPaint;
|
||||
private static Tile sFreeTileHead = null;
|
||||
|
||||
static {
|
||||
sUploadBitmap = Bitmap.createBitmap(TILE_SIZE, TILE_SIZE, Bitmap.Config.ARGB_8888);
|
||||
sCanvas = new Canvas(sUploadBitmap);
|
||||
sBitmapPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
|
||||
sBitmapPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
|
||||
sPaint = new Paint();
|
||||
sPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
|
||||
sPaint.setColor(Color.TRANSPARENT);
|
||||
}
|
||||
|
||||
private final Tile[] mTiles; // Can be modified in different threads.
|
||||
// Should be protected by "synchronized."
|
||||
private final int mWidth;
|
||||
private final int mHeight;
|
||||
private final RectF mSrcRect = new RectF();
|
||||
private final RectF mDestRect = new RectF();
|
||||
private int mUploadIndex = 0;
|
||||
|
||||
public TiledTexture(Bitmap bitmap, boolean isOpaque) {
|
||||
mWidth = bitmap.getWidth();
|
||||
mHeight = bitmap.getHeight();
|
||||
ArrayList<Tile> list = new ArrayList<>();
|
||||
|
||||
for (int x = 0, w = mWidth; x < w; x += CONTENT_SIZE) {
|
||||
for (int y = 0, h = mHeight; y < h; y += CONTENT_SIZE) {
|
||||
Tile tile = obtainTile();
|
||||
tile.offsetX = x;
|
||||
tile.offsetY = y;
|
||||
tile.bitmap = bitmap;
|
||||
tile.setSize(
|
||||
Math.min(CONTENT_SIZE, mWidth - x),
|
||||
Math.min(CONTENT_SIZE, mHeight - y));
|
||||
tile.setOpaque(isOpaque);
|
||||
list.add(tile);
|
||||
}
|
||||
}
|
||||
mTiles = list.toArray(new Tile[list.size()]);
|
||||
}
|
||||
|
||||
private static void freeTile(Tile tile) {
|
||||
tile.invalidateContent();
|
||||
tile.bitmap = null;
|
||||
synchronized (sFreeTileLock) {
|
||||
tile.nextFreeTile = sFreeTileHead;
|
||||
sFreeTileHead = tile;
|
||||
}
|
||||
}
|
||||
|
||||
private static Tile obtainTile() {
|
||||
synchronized (sFreeTileLock) {
|
||||
Tile result = sFreeTileHead;
|
||||
if (result == null) return new Tile();
|
||||
sFreeTileHead = result.nextFreeTile;
|
||||
result.nextFreeTile = null;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// We want to draw the "source" on the "target".
|
||||
// This method is to find the "output" rectangle which is
|
||||
// the corresponding area of the "src".
|
||||
// (x,y) target
|
||||
// (x0,y0) source +---------------+
|
||||
// +----------+ | |
|
||||
// | src | | output |
|
||||
// | +--+ | linear map | +----+ |
|
||||
// | +--+ | ----------> | | | |
|
||||
// | | by (scaleX, scaleY) | +----+ |
|
||||
// +----------+ | |
|
||||
// Texture +---------------+
|
||||
// Canvas
|
||||
private static void mapRect(RectF output,
|
||||
RectF src, float x0, float y0, float x, float y, float scaleX,
|
||||
float scaleY) {
|
||||
output.set(x + (src.left - x0) * scaleX,
|
||||
y + (src.top - y0) * scaleY,
|
||||
x + (src.right - x0) * scaleX,
|
||||
y + (src.bottom - y0) * scaleY);
|
||||
}
|
||||
|
||||
private boolean uploadNextTile(GLCanvas canvas) {
|
||||
if (mUploadIndex == mTiles.length) return true;
|
||||
|
||||
synchronized (mTiles) {
|
||||
Tile next = mTiles[mUploadIndex++];
|
||||
|
||||
// Make sure tile has not already been recycled by the time
|
||||
// this is called (race condition in onGLIdle)
|
||||
if (next.bitmap != null) {
|
||||
boolean hasBeenLoad = next.isLoaded();
|
||||
next.updateContent(canvas);
|
||||
|
||||
// It will take some time for a texture to be drawn for the first
|
||||
// time. When scrolling, we need to draw several tiles on the screen
|
||||
// at the same time. It may cause a UI jank even these textures has
|
||||
// been uploaded.
|
||||
if (!hasBeenLoad) next.draw(canvas, 0, 0);
|
||||
}
|
||||
}
|
||||
return mUploadIndex == mTiles.length;
|
||||
}
|
||||
|
||||
public boolean isReady() {
|
||||
return mUploadIndex == mTiles.length;
|
||||
}
|
||||
|
||||
// Can be called in UI thread.
|
||||
public void recycle() {
|
||||
synchronized (mTiles) {
|
||||
for (int i = 0, n = mTiles.length; i < n; ++i) {
|
||||
freeTile(mTiles[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draws a mixed color of this texture and a specified color onto the
|
||||
// a rectangle. The used color is: from * (1 - ratio) + to * ratio.
|
||||
public void drawMixed(GLCanvas canvas, int color, float ratio,
|
||||
int x, int y, int width, int height) {
|
||||
RectF src = mSrcRect;
|
||||
RectF dest = mDestRect;
|
||||
float scaleX = (float) width / mWidth;
|
||||
float scaleY = (float) height / mHeight;
|
||||
synchronized (mTiles) {
|
||||
for (int i = 0, n = mTiles.length; i < n; ++i) {
|
||||
Tile t = mTiles[i];
|
||||
src.set(0, 0, t.contentWidth, t.contentHeight);
|
||||
src.offset(t.offsetX, t.offsetY);
|
||||
mapRect(dest, src, 0, 0, x, y, scaleX, scaleY);
|
||||
src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
|
||||
canvas.drawMixed(t, color, ratio, mSrcRect, mDestRect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void drawMixed(GLCanvas canvas, int color, float ratio,
|
||||
RectF source, RectF target) {
|
||||
RectF src = mSrcRect;
|
||||
RectF dest = mDestRect;
|
||||
float x0 = source.left;
|
||||
float y0 = source.top;
|
||||
float x = target.left;
|
||||
float y = target.top;
|
||||
float scaleX = target.width() / source.width();
|
||||
float scaleY = target.height() / source.height();
|
||||
|
||||
synchronized (mTiles) {
|
||||
for (int i = 0, n = mTiles.length; i < n; ++i) {
|
||||
Tile t = mTiles[i];
|
||||
src.set(0, 0, t.contentWidth, t.contentHeight);
|
||||
src.offset(t.offsetX, t.offsetY);
|
||||
if (!src.intersect(source)) continue;
|
||||
mapRect(dest, src, x0, y0, x, y, scaleX, scaleY);
|
||||
src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
|
||||
canvas.drawMixed(t, color, ratio, mSrcRect, mDestRect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draws the texture on to the specified rectangle.
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, int x, int y, int width, int height) {
|
||||
RectF src = mSrcRect;
|
||||
RectF dest = mDestRect;
|
||||
float scaleX = (float) width / mWidth;
|
||||
float scaleY = (float) height / mHeight;
|
||||
synchronized (mTiles) {
|
||||
for (int i = 0, n = mTiles.length; i < n; ++i) {
|
||||
Tile t = mTiles[i];
|
||||
src.set(0, 0, t.contentWidth, t.contentHeight);
|
||||
src.offset(t.offsetX, t.offsetY);
|
||||
mapRect(dest, src, 0, 0, x, y, scaleX, scaleY);
|
||||
src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
|
||||
canvas.drawTexture(t, mSrcRect, mDestRect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draws a sub region of this texture on to the specified rectangle.
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, RectF source, RectF target) {
|
||||
RectF src = mSrcRect;
|
||||
RectF dest = mDestRect;
|
||||
float x0 = source.left;
|
||||
float y0 = source.top;
|
||||
float x = target.left;
|
||||
float y = target.top;
|
||||
float scaleX = target.width() / source.width();
|
||||
float scaleY = target.height() / source.height();
|
||||
|
||||
synchronized (mTiles) {
|
||||
for (int i = 0, n = mTiles.length; i < n; ++i) {
|
||||
Tile t = mTiles[i];
|
||||
src.set(0, 0, t.contentWidth, t.contentHeight);
|
||||
src.offset(t.offsetX, t.offsetY);
|
||||
if (!src.intersect(source)) continue;
|
||||
mapRect(dest, src, x0, y0, x, y, scaleX, scaleY);
|
||||
src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
|
||||
canvas.drawTexture(t, src, dest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return mWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return mHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, int x, int y) {
|
||||
draw(canvas, x, y, mWidth, mHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpaque() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static class Uploader implements GLRoot.OnGLIdleListener {
|
||||
private final ArrayDeque<TiledTexture> mTextures =
|
||||
new ArrayDeque<>(INIT_CAPACITY);
|
||||
|
||||
private final GLRoot mGlRoot;
|
||||
private boolean mIsQueued = false;
|
||||
|
||||
public Uploader(GLRoot glRoot) {
|
||||
mGlRoot = glRoot;
|
||||
}
|
||||
|
||||
public synchronized void clear() {
|
||||
mTextures.clear();
|
||||
}
|
||||
|
||||
public synchronized void addTexture(TiledTexture t) {
|
||||
if (t.isReady()) return;
|
||||
mTextures.addLast(t);
|
||||
|
||||
if (mIsQueued) return;
|
||||
mIsQueued = true;
|
||||
mGlRoot.addOnGLIdleListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) {
|
||||
ArrayDeque<TiledTexture> deque = mTextures;
|
||||
synchronized (this) {
|
||||
long now = SystemClock.uptimeMillis();
|
||||
long dueTime = now + UPLOAD_TILE_LIMIT;
|
||||
while (now < dueTime && !deque.isEmpty()) {
|
||||
TiledTexture t = deque.peekFirst();
|
||||
if (t.uploadNextTile(canvas)) {
|
||||
deque.removeFirst();
|
||||
mGlRoot.requestRender();
|
||||
}
|
||||
now = SystemClock.uptimeMillis();
|
||||
}
|
||||
mIsQueued = !mTextures.isEmpty();
|
||||
|
||||
// return true to keep this listener in the queue
|
||||
return mIsQueued;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class Tile extends UploadedTexture {
|
||||
public int offsetX;
|
||||
public int offsetY;
|
||||
public Bitmap bitmap;
|
||||
public Tile nextFreeTile;
|
||||
public int contentWidth;
|
||||
public int contentHeight;
|
||||
|
||||
@Override
|
||||
public void setSize(int width, int height) {
|
||||
contentWidth = width;
|
||||
contentHeight = height;
|
||||
mWidth = width + 2 * BORDER_SIZE;
|
||||
mHeight = height + 2 * BORDER_SIZE;
|
||||
mTextureWidth = TILE_SIZE;
|
||||
mTextureHeight = TILE_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bitmap onGetBitmap() {
|
||||
// make a local copy of the reference to the bitmap,
|
||||
// since it might be null'd in a different thread. b/8694871
|
||||
Bitmap localBitmapRef = bitmap;
|
||||
bitmap = null;
|
||||
|
||||
if (localBitmapRef != null) {
|
||||
int x = BORDER_SIZE - offsetX;
|
||||
int y = BORDER_SIZE - offsetY;
|
||||
int r = localBitmapRef.getWidth() + x;
|
||||
int b = localBitmapRef.getHeight() + y;
|
||||
sCanvas.drawBitmap(localBitmapRef, x, y, sBitmapPaint);
|
||||
localBitmapRef = null;
|
||||
|
||||
// draw borders if need
|
||||
if (x > 0) sCanvas.drawLine(x - 1, 0, x - 1, TILE_SIZE, sPaint);
|
||||
if (y > 0) sCanvas.drawLine(0, y - 1, TILE_SIZE, y - 1, sPaint);
|
||||
if (r < CONTENT_SIZE) sCanvas.drawLine(r, 0, r, TILE_SIZE, sPaint);
|
||||
if (b < CONTENT_SIZE) sCanvas.drawLine(0, b, TILE_SIZE, b, sPaint);
|
||||
}
|
||||
|
||||
return sUploadBitmap;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFreeBitmap(Bitmap bitmap) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,296 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.glrenderer;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Bitmap.Config;
|
||||
import android.opengl.GLUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import javax.microedition.khronos.opengles.GL11;
|
||||
|
||||
// UploadedTextures use a Bitmap for the content of the texture.
|
||||
//
|
||||
// Subclasses should implement onGetBitmap() to provide the Bitmap and
|
||||
// implement onFreeBitmap(mBitmap) which will be called when the Bitmap
|
||||
// is not needed anymore.
|
||||
//
|
||||
// isContentValid() is meaningful only when the isLoaded() returns true.
|
||||
// It means whether the content needs to be updated.
|
||||
//
|
||||
// The user of this class should call recycle() when the texture is not
|
||||
// needed anymore.
|
||||
//
|
||||
// By default an UploadedTexture is opaque (so it can be drawn faster without
|
||||
// blending). The user or subclass can override it using setOpaque().
|
||||
public abstract class UploadedTexture extends BasicTexture {
|
||||
|
||||
// To prevent keeping allocation the borders, we store those used borders here.
|
||||
// Since the length will be power of two, it won't use too much memory.
|
||||
private static final HashMap<BorderKey, Bitmap> sBorderLines =
|
||||
new HashMap<>();
|
||||
private static final BorderKey sBorderKey = new BorderKey();
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = "Texture";
|
||||
private static final int UPLOAD_LIMIT = 100;
|
||||
private static int sUploadedCount;
|
||||
protected Bitmap mBitmap;
|
||||
private boolean mContentValid = true;
|
||||
// indicate this textures is being uploaded in background
|
||||
private boolean mIsUploading = false;
|
||||
private boolean mOpaque = true;
|
||||
private boolean mThrottled = false;
|
||||
private int mBorder;
|
||||
|
||||
protected UploadedTexture() {
|
||||
this(false);
|
||||
}
|
||||
|
||||
protected UploadedTexture(boolean hasBorder) {
|
||||
super(null, 0, STATE_UNLOADED);
|
||||
if (hasBorder) {
|
||||
setBorder(true);
|
||||
mBorder = 1;
|
||||
}
|
||||
}
|
||||
|
||||
private static Bitmap getBorderLine(
|
||||
boolean vertical, Config config, int length) {
|
||||
BorderKey key = sBorderKey;
|
||||
key.vertical = vertical;
|
||||
key.config = config;
|
||||
key.length = length;
|
||||
Bitmap bitmap = sBorderLines.get(key);
|
||||
if (bitmap == null) {
|
||||
bitmap = vertical
|
||||
? Bitmap.createBitmap(1, length, config)
|
||||
: Bitmap.createBitmap(length, 1, config);
|
||||
sBorderLines.put(key.clone(), bitmap);
|
||||
}
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
public static void resetUploadLimit() {
|
||||
sUploadedCount = 0;
|
||||
}
|
||||
|
||||
public static boolean uploadLimitReached() {
|
||||
return sUploadedCount > UPLOAD_LIMIT;
|
||||
}
|
||||
|
||||
protected void setIsUploading(boolean uploading) {
|
||||
mIsUploading = uploading;
|
||||
}
|
||||
|
||||
public boolean isUploading() {
|
||||
return mIsUploading;
|
||||
}
|
||||
|
||||
protected void setThrottled(boolean throttled) {
|
||||
mThrottled = throttled;
|
||||
}
|
||||
|
||||
private Bitmap getBitmap() {
|
||||
if (mBitmap == null) {
|
||||
mBitmap = onGetBitmap();
|
||||
if (mWidth == UNSPECIFIED) {
|
||||
int w = mBitmap.getWidth() + mBorder * 2;
|
||||
int h = mBitmap.getHeight() + mBorder * 2;
|
||||
setSize(w, h);
|
||||
}
|
||||
}
|
||||
return mBitmap;
|
||||
}
|
||||
|
||||
private void freeBitmap() {
|
||||
if (mBitmap == null) {
|
||||
throw new IllegalStateException("mBitmap == null");
|
||||
}
|
||||
onFreeBitmap(mBitmap);
|
||||
mBitmap = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
if (mWidth == UNSPECIFIED) getBitmap();
|
||||
return mWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
if (mWidth == UNSPECIFIED) getBitmap();
|
||||
return mHeight;
|
||||
}
|
||||
|
||||
protected abstract Bitmap onGetBitmap();
|
||||
|
||||
protected abstract void onFreeBitmap(Bitmap bitmap);
|
||||
|
||||
public void invalidateContent() {
|
||||
if (mBitmap != null) freeBitmap();
|
||||
mContentValid = false;
|
||||
mWidth = UNSPECIFIED;
|
||||
mHeight = UNSPECIFIED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the content on GPU is valid.
|
||||
*/
|
||||
public boolean isContentValid() {
|
||||
return isLoaded() && mContentValid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the content on GPU's memory.
|
||||
*
|
||||
* @param canvas
|
||||
*/
|
||||
public void updateContent(GLCanvas canvas) {
|
||||
if (!isLoaded()) {
|
||||
if (mThrottled && ++sUploadedCount > UPLOAD_LIMIT) {
|
||||
return;
|
||||
}
|
||||
uploadToCanvas(canvas);
|
||||
} else if (!mContentValid) {
|
||||
Bitmap bitmap = getBitmap();
|
||||
int format = GLUtils.getInternalFormat(bitmap);
|
||||
int type = GLUtils.getType(bitmap);
|
||||
canvas.texSubImage2D(this, mBorder, mBorder, bitmap, format, type);
|
||||
freeBitmap();
|
||||
mContentValid = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void uploadToCanvas(GLCanvas canvas) {
|
||||
|
||||
Bitmap bitmap = getBitmap();
|
||||
if (bitmap != null) {
|
||||
try {
|
||||
int bWidth = bitmap.getWidth();
|
||||
int bHeight = bitmap.getHeight();
|
||||
int texWidth = getTextureWidth();
|
||||
int texHeight = getTextureHeight();
|
||||
|
||||
if (bWidth > texWidth || bHeight > texHeight) {
|
||||
throw new IllegalStateException("bWidth > texWidth || bHeight > texHeight");
|
||||
}
|
||||
|
||||
// Upload the bitmap to a new texture.
|
||||
mId = canvas.getGLId().generateTexture();
|
||||
canvas.setTextureParameters(this);
|
||||
|
||||
if (bWidth == texWidth && bHeight == texHeight) {
|
||||
canvas.initializeTexture(this, bitmap);
|
||||
} else {
|
||||
int format = GLUtils.getInternalFormat(bitmap);
|
||||
int type = GLUtils.getType(bitmap);
|
||||
Config config = bitmap.getConfig();
|
||||
|
||||
canvas.initializeTextureSize(this, format, type);
|
||||
canvas.texSubImage2D(this, mBorder, mBorder, bitmap, format, type);
|
||||
|
||||
if (mBorder > 0) {
|
||||
// Left border
|
||||
Bitmap line = getBorderLine(true, config, texHeight);
|
||||
canvas.texSubImage2D(this, 0, 0, line, format, type);
|
||||
|
||||
// Top border
|
||||
line = getBorderLine(false, config, texWidth);
|
||||
canvas.texSubImage2D(this, 0, 0, line, format, type);
|
||||
}
|
||||
|
||||
// Right border
|
||||
if (mBorder + bWidth < texWidth) {
|
||||
Bitmap line = getBorderLine(true, config, texHeight);
|
||||
canvas.texSubImage2D(this, mBorder + bWidth, 0, line, format, type);
|
||||
}
|
||||
|
||||
// Bottom border
|
||||
if (mBorder + bHeight < texHeight) {
|
||||
Bitmap line = getBorderLine(false, config, texWidth);
|
||||
canvas.texSubImage2D(this, 0, mBorder + bHeight, line, format, type);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
freeBitmap();
|
||||
}
|
||||
// Update texture state.
|
||||
setAssociatedCanvas(canvas);
|
||||
mState = STATE_LOADED;
|
||||
mContentValid = true;
|
||||
} else {
|
||||
mState = STATE_ERROR;
|
||||
throw new RuntimeException("Texture load fail, no bitmap");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onBind(GLCanvas canvas) {
|
||||
updateContent(canvas);
|
||||
return isContentValid();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getTarget() {
|
||||
return GL11.GL_TEXTURE_2D;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpaque() {
|
||||
return mOpaque;
|
||||
}
|
||||
|
||||
public void setOpaque(boolean isOpaque) {
|
||||
mOpaque = isOpaque;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recycle() {
|
||||
super.recycle();
|
||||
if (mBitmap != null) freeBitmap();
|
||||
}
|
||||
|
||||
private static class BorderKey implements Cloneable {
|
||||
public boolean vertical;
|
||||
public Config config;
|
||||
public int length;
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int x = config.hashCode() ^ length;
|
||||
return vertical ? x : -x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (!(object instanceof BorderKey o)) return false;
|
||||
return vertical == o.vertical
|
||||
&& config == o.config && length == o.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BorderKey clone() {
|
||||
try {
|
||||
return (BorderKey) super.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.glrenderer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
public class XmlResourceTexture extends UploadedTexture {
|
||||
|
||||
private final Context mContext;
|
||||
private final int mResId;
|
||||
private int mWidth;
|
||||
private int mHeight;
|
||||
|
||||
public XmlResourceTexture(Context context, @DrawableRes int resId) {
|
||||
mContext = context;
|
||||
mResId = resId;
|
||||
setOpaque(false);
|
||||
}
|
||||
|
||||
public void setBound(int width, int height) {
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
invalidateContent();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bitmap onGetBitmap() {
|
||||
Drawable drawable = ContextCompat.getDrawable(mContext, mResId);
|
||||
|
||||
int width = mWidth;
|
||||
int height = mHeight;
|
||||
if (width <= 0) {
|
||||
width = drawable.getIntrinsicWidth();
|
||||
}
|
||||
if (height <= 0) {
|
||||
height = drawable.getIntrinsicHeight();
|
||||
}
|
||||
if (width <= 0) {
|
||||
width = 1;
|
||||
}
|
||||
if (height <= 0) {
|
||||
height = 1;
|
||||
}
|
||||
|
||||
drawable.setBounds(0, 0, width, height);
|
||||
|
||||
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
drawable.draw(canvas);
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFreeBitmap(Bitmap bitmap) {
|
||||
if (!inFinalizer()) {
|
||||
bitmap.recycle();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.image;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.hippo.glview.glrenderer.GLCanvas;
|
||||
import com.hippo.glview.view.GLView;
|
||||
import com.hippo.glview.view.Gravity;
|
||||
import com.hippo.yorozuya.collect.CollectionUtils;
|
||||
|
||||
public class GLImageMovableTextView extends GLView {
|
||||
|
||||
ImageMovableTextTexture mTextTexture;
|
||||
|
||||
private String mText = "";
|
||||
private int[] mIndexes = CollectionUtils.EMPTY_INT_ARRAY;
|
||||
|
||||
private int mGravity = Gravity.NO_GRAVITY;
|
||||
|
||||
private void generateIndexes() {
|
||||
if (mTextTexture == null || TextUtils.isEmpty(mText)) {
|
||||
mIndexes = CollectionUtils.EMPTY_INT_ARRAY;
|
||||
} else {
|
||||
mIndexes = mTextTexture.getTextIndexes(mText);
|
||||
}
|
||||
}
|
||||
|
||||
public void setTextTexture(ImageMovableTextTexture textTexture) {
|
||||
if (mTextTexture == textTexture) {
|
||||
return;
|
||||
}
|
||||
mTextTexture = textTexture;
|
||||
|
||||
generateIndexes();
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
public void setText(String text) {
|
||||
if (text == null) {
|
||||
text = "";
|
||||
}
|
||||
if (text.equals(mText)) {
|
||||
return;
|
||||
}
|
||||
mText = text;
|
||||
|
||||
generateIndexes();
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
public void setGravity(int gravity) {
|
||||
if (mGravity != gravity) {
|
||||
mGravity = gravity;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getSuggestedMinimumWidth() {
|
||||
if (mTextTexture == null) {
|
||||
return super.getSuggestedMinimumWidth();
|
||||
} else {
|
||||
return Math.max((int) mTextTexture.getTextWidth(mIndexes) + mPaddings.left + mPaddings.right,
|
||||
super.getSuggestedMinimumWidth());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getSuggestedMinimumHeight() {
|
||||
if (mTextTexture == null) {
|
||||
return super.getSuggestedMinimumHeight();
|
||||
} else {
|
||||
return Math.max((int) mTextTexture.getTextHeight() + mPaddings.top + mPaddings.bottom,
|
||||
super.getSuggestedMinimumHeight());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRender(GLCanvas canvas) {
|
||||
if (mTextTexture == null || mIndexes == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Rect paddings = getPaddings();
|
||||
int x = getDefaultBegin(getWidth(), (int) mTextTexture.getTextWidth(mIndexes),
|
||||
paddings.left, paddings.right, Gravity.getPosition(mGravity, Gravity.HORIZONTAL));
|
||||
int y = getDefaultBegin(getHeight(), (int) mTextTexture.getTextHeight(),
|
||||
paddings.top, paddings.bottom, Gravity.getPosition(mGravity, Gravity.VERTICAL));
|
||||
mTextTexture.drawText(canvas, mIndexes, x, y);
|
||||
}
|
||||
}
|
||||
@@ -1,210 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.image;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Typeface;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.glview.glrenderer.GLCanvas;
|
||||
import com.hippo.image.Image;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class ImageMovableTextTexture extends ImageSpriteTexture {
|
||||
|
||||
private final char[] mCharacters;
|
||||
private final float[] mWidths;
|
||||
private final float mHeight;
|
||||
private final float mMaxWidth;
|
||||
|
||||
public ImageMovableTextTexture(@NonNull ImageWrapper image, int count, int[] rects,
|
||||
char[] characters, float[] widths, float height, float maxWidth) {
|
||||
super(image, count, rects);
|
||||
|
||||
mCharacters = characters;
|
||||
mWidths = widths;
|
||||
mHeight = height;
|
||||
mMaxWidth = maxWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a TextTexture to draw text
|
||||
*
|
||||
* @param typeface the typeface
|
||||
* @param size text size
|
||||
* @param characters all Characters
|
||||
* @return the TextTexture
|
||||
*/
|
||||
@Nullable
|
||||
public static ImageMovableTextTexture create(Typeface typeface, int size, int color, char[] characters) {
|
||||
Paint paint = new Paint();
|
||||
paint.setAntiAlias(true);
|
||||
paint.setTextSize(size);
|
||||
paint.setColor(color);
|
||||
paint.setTypeface(typeface);
|
||||
|
||||
Paint.FontMetricsInt fmi = paint.getFontMetricsInt();
|
||||
int fixed = fmi.bottom;
|
||||
int height = fmi.bottom - fmi.top;
|
||||
|
||||
int length = characters.length;
|
||||
float[] widths = new float[length];
|
||||
paint.getTextWidths(characters, 0, length, widths);
|
||||
|
||||
// Calculate bitmap size
|
||||
float maxWidth = 0.0f;
|
||||
for (float f : widths) {
|
||||
maxWidth = Math.max(maxWidth, f);
|
||||
}
|
||||
int hCount = (int) Math.ceil(Math.sqrt(height / maxWidth * length));
|
||||
int vCount = (int) Math.ceil(Math.sqrt(maxWidth / height * length));
|
||||
if (hCount * (vCount - 1) > length) {
|
||||
vCount--;
|
||||
}
|
||||
if ((hCount - 1) * vCount > length) {
|
||||
hCount--;
|
||||
}
|
||||
|
||||
Bitmap bitmap = Bitmap.createBitmap((int) Math.ceil(hCount * maxWidth),
|
||||
(int) Math.ceil(vCount * height), Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
canvas.translate(0, height - fixed);
|
||||
|
||||
// Draw
|
||||
int[] rects = new int[length * 4];
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
for (int i = 0; i < length; i++) {
|
||||
int offset = i * 4;
|
||||
rects[offset] = x;
|
||||
rects[offset + 1] = y;
|
||||
rects[offset + 2] = (int) widths[i];
|
||||
rects[offset + 3] = height;
|
||||
|
||||
canvas.drawText(characters, i, 1, x, y, paint);
|
||||
|
||||
if (i % hCount == hCount - 1) {
|
||||
// The end of row
|
||||
x = 0;
|
||||
y += height;
|
||||
} else {
|
||||
x += maxWidth;
|
||||
}
|
||||
}
|
||||
|
||||
Image image = Image.create(bitmap);
|
||||
if (image == null) {
|
||||
return null;
|
||||
}
|
||||
ImageWrapper imageWrapper = new ImageWrapper(image);
|
||||
if (imageWrapper.obtain()) {
|
||||
return new ImageMovableTextTexture(imageWrapper, length, rects, characters, widths, height, maxWidth);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public int[] getTextIndexes(String text) {
|
||||
char[] characters = mCharacters;
|
||||
|
||||
int length = text.length();
|
||||
int[] indexes = new int[length];
|
||||
for (int i = 0; i < length; i++) {
|
||||
char ch = text.charAt(i);
|
||||
indexes[i] = Arrays.binarySearch(characters, ch);
|
||||
}
|
||||
|
||||
return indexes;
|
||||
}
|
||||
|
||||
public float getTextWidth(String text) {
|
||||
char[] characters = mCharacters;
|
||||
float[] widths = mWidths;
|
||||
float width = 0.0f;
|
||||
|
||||
for (int i = 0, n = text.length(); i < n; i++) {
|
||||
char ch = text.charAt(i);
|
||||
int index = Arrays.binarySearch(characters, ch);
|
||||
if (index >= 0) {
|
||||
width += widths[index];
|
||||
} else {
|
||||
width += mMaxWidth;
|
||||
}
|
||||
}
|
||||
|
||||
return width;
|
||||
}
|
||||
|
||||
public float getTextWidth(int[] indexes) {
|
||||
float[] widths = mWidths;
|
||||
float width = 0.0f;
|
||||
int length = indexes.length;
|
||||
for (int i = 0; i < length; i++) {
|
||||
int index = indexes[i];
|
||||
if (index >= 0) {
|
||||
width += widths[index];
|
||||
} else {
|
||||
width += mMaxWidth;
|
||||
}
|
||||
}
|
||||
|
||||
return width;
|
||||
}
|
||||
|
||||
public float getMaxWidth() {
|
||||
return mMaxWidth;
|
||||
}
|
||||
|
||||
public float getTextHeight() {
|
||||
return mHeight;
|
||||
}
|
||||
|
||||
public void drawText(GLCanvas canvas, String text, int x, int y) {
|
||||
char[] characters = mCharacters;
|
||||
float[] widths = mWidths;
|
||||
|
||||
for (int i = 0, n = text.length(); i < n; i++) {
|
||||
char ch = text.charAt(i);
|
||||
int index = Arrays.binarySearch(characters, ch);
|
||||
if (index >= 0) {
|
||||
drawSprite(canvas, index, x, y);
|
||||
x += widths[index];
|
||||
} else {
|
||||
x += mMaxWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void drawText(GLCanvas canvas, int[] indexes, int x, int y) {
|
||||
float[] widths = mWidths;
|
||||
|
||||
for (int i = 0, n = indexes.length; i < n; i++) {
|
||||
int index = indexes[i];
|
||||
if (index >= 0) {
|
||||
drawSprite(canvas, index, x, y);
|
||||
x += widths[index];
|
||||
} else {
|
||||
x += mMaxWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.image;
|
||||
|
||||
import android.graphics.RectF;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.hippo.glview.glrenderer.GLCanvas;
|
||||
import com.hippo.yorozuya.AssertUtils;
|
||||
|
||||
public class ImageSpriteTexture extends ImageTexture {
|
||||
|
||||
private final int mCount;
|
||||
private final int[] mRects;
|
||||
|
||||
private final RectF mTempSource = new RectF();
|
||||
private final RectF mTempTarget = new RectF();
|
||||
|
||||
public ImageSpriteTexture(@NonNull ImageWrapper image, int count, int[] rects) {
|
||||
super(image);
|
||||
|
||||
AssertUtils.assertEquals("rects.length must be count * 4", count * 4, rects.length);
|
||||
mCount = count;
|
||||
mRects = rects;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return mCount;
|
||||
}
|
||||
|
||||
public void drawSprite(GLCanvas canvas, int index, int x, int y) {
|
||||
int[] rects = mRects;
|
||||
int offset = index * 4;
|
||||
int sourceX = rects[offset];
|
||||
int sourceY = rects[offset + 1];
|
||||
int sourceWidth = rects[offset + 2];
|
||||
int sourceHeight = rects[offset + 3];
|
||||
mTempSource.set(sourceX, sourceY, sourceX + sourceWidth, sourceY + sourceHeight);
|
||||
mTempTarget.set(x, y, x + sourceWidth, y + sourceHeight);
|
||||
draw(canvas, mTempSource, mTempTarget);
|
||||
}
|
||||
|
||||
public void drawSprite(GLCanvas canvas, int index, int x, int y, int width, int height) {
|
||||
int[] rects = mRects;
|
||||
int offset = index * 4;
|
||||
int sourceX = rects[offset];
|
||||
int sourceY = rects[offset + 1];
|
||||
mTempSource.set(sourceX, sourceY, sourceX + rects[offset + 2], sourceY + rects[offset + 3]);
|
||||
mTempTarget.set(x, y, x + width, y + height);
|
||||
draw(canvas, mTempSource, mTempTarget);
|
||||
}
|
||||
|
||||
public void drawSpriteMixed(GLCanvas canvas, int index, int color, float ratio, int x, int y) {
|
||||
int[] rects = mRects;
|
||||
int offset = index * 4;
|
||||
int sourceX = rects[offset];
|
||||
int sourceY = rects[offset + 1];
|
||||
int sourceWidth = rects[offset + 2];
|
||||
int sourceHeight = rects[offset + 3];
|
||||
mTempSource.set(sourceX, sourceY, sourceX + sourceWidth, sourceY + sourceHeight);
|
||||
mTempTarget.set(x, y, x + sourceWidth, y + sourceHeight);
|
||||
drawMixed(canvas, color, ratio, mTempSource, mTempTarget);
|
||||
}
|
||||
|
||||
public void drawSpriteMixed(GLCanvas canvas, int index, int color, float ratio,
|
||||
int x, int y, int width, int height) {
|
||||
int[] rects = mRects;
|
||||
int offset = index * 4;
|
||||
int sourceX = rects[offset];
|
||||
int sourceY = rects[offset + 1];
|
||||
mTempSource.set(sourceX, sourceY, sourceX + rects[offset + 2],
|
||||
sourceY + rects[offset + 3]);
|
||||
mTempTarget.set(x, y, x + width, y + height);
|
||||
drawMixed(canvas, color, ratio, mTempSource, mTempTarget);
|
||||
}
|
||||
}
|
||||
@@ -1,678 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.image;
|
||||
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.os.Process;
|
||||
import android.os.SystemClock;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.hippo.glview.annotation.RenderThread;
|
||||
import com.hippo.glview.glrenderer.GLCanvas;
|
||||
import com.hippo.glview.glrenderer.NativeTexture;
|
||||
import com.hippo.glview.glrenderer.Texture;
|
||||
import com.hippo.glview.view.GLRoot;
|
||||
import com.hippo.yorozuya.thread.InfiniteThreadExecutor;
|
||||
import com.hippo.yorozuya.thread.PVLock;
|
||||
import com.hippo.yorozuya.thread.PriorityThreadFactory;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class ImageTexture implements Texture, Animatable {
|
||||
|
||||
private static final int TILE_SMALL = 0;
|
||||
private static final int TILE_LARGE = 1;
|
||||
private static final int SMALL_CONTENT_SIZE = 254;
|
||||
private static final int SMALL_BORDER_SIZE = 1;
|
||||
private static final int SMALL_TILE_SIZE = SMALL_CONTENT_SIZE + 2 * SMALL_BORDER_SIZE;
|
||||
private static final int LARGE_CONTENT_SIZE = SMALL_CONTENT_SIZE * 2;
|
||||
private static final int LARGE_BORDER_SIZE = SMALL_BORDER_SIZE * 2;
|
||||
private static final int LARGE_TILE_SIZE = LARGE_CONTENT_SIZE + 2 * LARGE_BORDER_SIZE;
|
||||
private static final int INIT_CAPACITY = 8;
|
||||
// We are targeting at 60fps, so we have 16ms for each frame.
|
||||
// In this 16ms, we use about 4~8 ms to upload tiles.
|
||||
private static final long UPLOAD_TILE_LIMIT = 4; // ms
|
||||
private static final Executor sThreadExecutor;
|
||||
private static final PVLock sPVLock;
|
||||
private static final Object sFreeTileLock = new Object();
|
||||
private static Tile sSmallFreeTileHead = null;
|
||||
private static Tile sLargeFreeTileHead = null;
|
||||
|
||||
static {
|
||||
sThreadExecutor = new InfiniteThreadExecutor(10 * 1000, new LinkedList<Runnable>(),
|
||||
new PriorityThreadFactory("ImageTexture$AnimateTask", Process.THREAD_PRIORITY_BACKGROUND));
|
||||
sPVLock = new PVLock(3);
|
||||
}
|
||||
|
||||
private final ImageWrapper mImage;
|
||||
private final Tile[] mTiles; // Can be modified in different threads.
|
||||
private final int mWidth;
|
||||
// Should be protected by "synchronized."
|
||||
private final int mHeight;
|
||||
private final boolean mOpaque;
|
||||
private final RectF mSrcRect = new RectF();
|
||||
private final RectF mDestRect = new RectF();
|
||||
private final AtomicBoolean mRunning = new AtomicBoolean();
|
||||
private final AtomicBoolean mRequestAnimation = new AtomicBoolean();
|
||||
private final AtomicBoolean mFrameDirty = new AtomicBoolean();
|
||||
private final AtomicBoolean mNeedRelease = new AtomicBoolean();
|
||||
private final AtomicBoolean mReleased = new AtomicBoolean();
|
||||
private int mUploadIndex = 0;
|
||||
private boolean mImageBusy = false;
|
||||
private Runnable mAnimateRunnable = null;
|
||||
|
||||
private WeakReference<Callback> mCallback;
|
||||
|
||||
/**
|
||||
* Call {@link ImageWrapper#obtain()} first
|
||||
*/
|
||||
public ImageTexture(@NonNull ImageWrapper image) {
|
||||
mImage = image;
|
||||
int width = mWidth = image.getWidth();
|
||||
int height = mHeight = image.getHeight();
|
||||
boolean opaque = mOpaque = image.isOpaque();
|
||||
ArrayList<Tile> list = new ArrayList<>();
|
||||
|
||||
for (int x = 0; x < width; x += LARGE_CONTENT_SIZE) {
|
||||
for (int y = 0; y < height; y += LARGE_CONTENT_SIZE) {
|
||||
int w = Math.min(LARGE_CONTENT_SIZE, width - x);
|
||||
int h = Math.min(LARGE_CONTENT_SIZE, height - y);
|
||||
|
||||
if (w <= SMALL_CONTENT_SIZE) {
|
||||
Tile tile = obtainSmallTile();
|
||||
tile.offsetX = x;
|
||||
tile.offsetY = y;
|
||||
tile.image = image;
|
||||
tile.setSize(TILE_SMALL, w, Math.min(SMALL_CONTENT_SIZE, h));
|
||||
tile.setOpaque(opaque);
|
||||
list.add(tile);
|
||||
|
||||
int nextHeight = h - SMALL_CONTENT_SIZE;
|
||||
if (nextHeight > 0) {
|
||||
Tile nextTile = obtainSmallTile();
|
||||
nextTile.offsetX = x;
|
||||
nextTile.offsetY = y + SMALL_CONTENT_SIZE;
|
||||
nextTile.image = image;
|
||||
nextTile.setSize(TILE_SMALL, w, nextHeight);
|
||||
nextTile.setOpaque(opaque);
|
||||
list.add(nextTile);
|
||||
}
|
||||
} else if (h <= SMALL_CONTENT_SIZE) {
|
||||
Tile tile = obtainSmallTile();
|
||||
tile.offsetX = x;
|
||||
tile.offsetY = y;
|
||||
tile.image = image;
|
||||
tile.setSize(TILE_SMALL, Math.min(SMALL_CONTENT_SIZE, w), h);
|
||||
tile.setOpaque(opaque);
|
||||
list.add(tile);
|
||||
|
||||
int nextWidth = w - SMALL_CONTENT_SIZE;
|
||||
if (nextWidth > 0) {
|
||||
Tile nextTile = obtainSmallTile();
|
||||
nextTile.offsetX = x + SMALL_CONTENT_SIZE;
|
||||
nextTile.offsetY = y;
|
||||
nextTile.image = image;
|
||||
nextTile.setSize(TILE_SMALL, nextWidth, h);
|
||||
nextTile.setOpaque(opaque);
|
||||
list.add(nextTile);
|
||||
}
|
||||
} else {
|
||||
Tile tile = obtainLargeTile();
|
||||
tile.offsetX = x;
|
||||
tile.offsetY = y;
|
||||
tile.image = image;
|
||||
tile.setSize(TILE_LARGE, w, h);
|
||||
tile.setOpaque(opaque);
|
||||
list.add(tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mTiles = list.toArray(new Tile[list.size()]);
|
||||
}
|
||||
|
||||
private static Tile obtainSmallTile() {
|
||||
synchronized (sFreeTileLock) {
|
||||
Tile result = sSmallFreeTileHead;
|
||||
if (result == null) {
|
||||
return new Tile();
|
||||
} else {
|
||||
sSmallFreeTileHead = result.nextFreeTile;
|
||||
result.nextFreeTile = null;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private static Tile obtainLargeTile() {
|
||||
synchronized (sFreeTileLock) {
|
||||
Tile result = sLargeFreeTileHead;
|
||||
if (result == null) {
|
||||
return new Tile();
|
||||
} else {
|
||||
sLargeFreeTileHead = result.nextFreeTile;
|
||||
result.nextFreeTile = null;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// We want to draw the "source" on the "target".
|
||||
// This method is to find the "output" rectangle which is
|
||||
// the corresponding area of the "src".
|
||||
// (x,y) target
|
||||
// (x0,y0) source +---------------+
|
||||
// +----------+ | |
|
||||
// | src | | output |
|
||||
// | +--+ | linear map | +----+ |
|
||||
// | +--+ | ----------> | | | |
|
||||
// | | by (scaleX, scaleY) | +----+ |
|
||||
// +----------+ | |
|
||||
// Texture +---------------+
|
||||
// Canvas
|
||||
private static void mapRect(RectF output,
|
||||
RectF src, float x0, float y0, float x, float y, float scaleX,
|
||||
float scaleY) {
|
||||
output.set(x + (src.left - x0) * scaleX,
|
||||
y + (src.top - y0) * scaleY,
|
||||
x + (src.right - x0) * scaleX,
|
||||
y + (src.bottom - y0) * scaleY);
|
||||
}
|
||||
|
||||
public Callback getCallback() {
|
||||
if (mCallback != null) {
|
||||
return mCallback.get();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public final void setCallback(Callback cb) {
|
||||
mCallback = new WeakReference<>(cb);
|
||||
}
|
||||
|
||||
public void invalidateSelf() {
|
||||
final Callback callback = getCallback();
|
||||
if (callback != null) {
|
||||
callback.invalidateImageTexture(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
synchronized (mImage) {
|
||||
if (!mImageBusy) {
|
||||
mImageBusy = true;
|
||||
} else {
|
||||
mRequestAnimation.lazySet(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
boolean end = mReleased.get() || mImage.isImageRecycled() || mNeedRelease.get() ||
|
||||
(!mImage.getAnimated()) || mRunning.get();
|
||||
|
||||
synchronized (mImage) {
|
||||
mImageBusy = false;
|
||||
}
|
||||
|
||||
if (end) {
|
||||
return;
|
||||
}
|
||||
|
||||
mRunning.lazySet(true);
|
||||
|
||||
synchronized (mImage) {
|
||||
if (mAnimateRunnable == null) {
|
||||
Runnable runnable = new AnimateRunnable();
|
||||
mAnimateRunnable = runnable;
|
||||
sThreadExecutor.execute(runnable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
mRunning.lazySet(false);
|
||||
mRequestAnimation.lazySet(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return mRunning.get();
|
||||
}
|
||||
|
||||
private boolean uploadNextTile(GLCanvas canvas) {
|
||||
if (mUploadIndex == mTiles.length) return true;
|
||||
|
||||
synchronized (mTiles) {
|
||||
Tile next = mTiles[mUploadIndex++];
|
||||
|
||||
// Make sure tile has not already been recycled by the time
|
||||
// this is called (race condition in onGLIdle)
|
||||
if (next.image != null) {
|
||||
boolean hasBeenLoad = next.isLoaded();
|
||||
next.updateContent(canvas);
|
||||
|
||||
// It will take some time for a texture to be drawn for the first
|
||||
// time. When scrolling, we need to draw several tiles on the screen
|
||||
// at the same time. It may cause a UI jank even these textures has
|
||||
// been uploaded.
|
||||
if (!hasBeenLoad) next.draw(canvas, 0, 0);
|
||||
}
|
||||
}
|
||||
return mUploadIndex == mTiles.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return mWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return mHeight;
|
||||
}
|
||||
|
||||
@RenderThread
|
||||
private void syncFrame() {
|
||||
if (mFrameDirty.getAndSet(false)) {
|
||||
// invalid tiles
|
||||
for (Tile tile : mTiles) {
|
||||
tile.invalidateContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, int x, int y) {
|
||||
draw(canvas, x, y, mWidth, mHeight);
|
||||
}
|
||||
|
||||
// Draws the texture on to the specified rectangle.
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, int x, int y, int w, int h) {
|
||||
RectF src = mSrcRect;
|
||||
RectF dest = mDestRect;
|
||||
float scaleX = (float) w / mWidth;
|
||||
float scaleY = (float) h / mHeight;
|
||||
|
||||
syncFrame();
|
||||
for (Tile t : mTiles) {
|
||||
src.set(0, 0, t.contentWidth, t.contentHeight);
|
||||
src.offset(t.offsetX, t.offsetY);
|
||||
mapRect(dest, src, 0, 0, x, y, scaleX, scaleY);
|
||||
src.offset(t.borderSize - t.offsetX, t.borderSize - t.offsetY);
|
||||
canvas.drawTexture(t, src, dest);
|
||||
}
|
||||
}
|
||||
|
||||
// Draws a sub region of this texture on to the specified rectangle.
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, RectF source, RectF target) {
|
||||
RectF src = mSrcRect;
|
||||
RectF dest = mDestRect;
|
||||
float x0 = source.left;
|
||||
float y0 = source.top;
|
||||
float x = target.left;
|
||||
float y = target.top;
|
||||
float scaleX = target.width() / source.width();
|
||||
float scaleY = target.height() / source.height();
|
||||
|
||||
syncFrame();
|
||||
for (Tile t : mTiles) {
|
||||
src.set(0, 0, t.contentWidth, t.contentHeight);
|
||||
src.offset(t.offsetX, t.offsetY);
|
||||
if (!src.intersect(source)) {
|
||||
continue;
|
||||
}
|
||||
mapRect(dest, src, x0, y0, x, y, scaleX, scaleY);
|
||||
src.offset(t.borderSize - t.offsetX, t.borderSize - t.offsetY);
|
||||
canvas.drawTexture(t, src, dest);
|
||||
}
|
||||
}
|
||||
|
||||
// Draws a mixed color of this texture and a specified color onto the
|
||||
// a rectangle. The used color is: from * (1 - ratio) + to * ratio.
|
||||
public void drawMixed(GLCanvas canvas, int color, float ratio,
|
||||
int x, int y, int width, int height) {
|
||||
RectF src = mSrcRect;
|
||||
RectF dest = mDestRect;
|
||||
float scaleX = (float) width / mWidth;
|
||||
float scaleY = (float) height / mHeight;
|
||||
|
||||
syncFrame();
|
||||
for (Tile t : mTiles) {
|
||||
src.set(0, 0, t.contentWidth, t.contentHeight);
|
||||
src.offset(t.offsetX, t.offsetY);
|
||||
mapRect(dest, src, 0, 0, x, y, scaleX, scaleY);
|
||||
src.offset(t.borderSize - t.offsetX, t.borderSize - t.offsetY);
|
||||
canvas.drawMixed(t, color, ratio, src, dest);
|
||||
}
|
||||
}
|
||||
|
||||
public void drawMixed(GLCanvas canvas, int color, float ratio,
|
||||
RectF source, RectF target) {
|
||||
RectF src = mSrcRect;
|
||||
RectF dest = mDestRect;
|
||||
float x0 = source.left;
|
||||
float y0 = source.top;
|
||||
float x = target.left;
|
||||
float y = target.top;
|
||||
float scaleX = target.width() / source.width();
|
||||
float scaleY = target.height() / source.height();
|
||||
|
||||
syncFrame();
|
||||
for (Tile t : mTiles) {
|
||||
src.set(0, 0, t.contentWidth, t.contentHeight);
|
||||
src.offset(t.offsetX, t.offsetY);
|
||||
if (!src.intersect(source)) {
|
||||
continue;
|
||||
}
|
||||
mapRect(dest, src, x0, y0, x, y, scaleX, scaleY);
|
||||
src.offset(t.borderSize - t.offsetX, t.borderSize - t.offsetY);
|
||||
canvas.drawMixed(t, color, ratio, src, dest);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpaque() {
|
||||
return mOpaque;
|
||||
}
|
||||
|
||||
public boolean isReady() {
|
||||
return mUploadIndex == mTiles.length;
|
||||
}
|
||||
|
||||
public void recycle() {
|
||||
mRunning.lazySet(false);
|
||||
|
||||
for (Tile mTile : mTiles) {
|
||||
mTile.free();
|
||||
}
|
||||
|
||||
boolean releaseNow;
|
||||
|
||||
synchronized (mImage) {
|
||||
if (!mImageBusy) {
|
||||
releaseNow = true;
|
||||
mImageBusy = true;
|
||||
} else {
|
||||
releaseNow = false;
|
||||
mNeedRelease.lazySet(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (releaseNow) {
|
||||
if (!mReleased.get()) {
|
||||
mImage.release();
|
||||
mReleased.lazySet(true);
|
||||
}
|
||||
synchronized (mImage) {
|
||||
mImageBusy = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@IntDef({TILE_SMALL, TILE_LARGE})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
private @interface TileType {
|
||||
}
|
||||
|
||||
public interface Callback {
|
||||
void invalidateImageTexture(ImageTexture who);
|
||||
}
|
||||
|
||||
public static class Uploader implements GLRoot.OnGLIdleListener {
|
||||
private final ArrayDeque<ImageTexture> mTextures =
|
||||
new ArrayDeque<>(INIT_CAPACITY);
|
||||
|
||||
private final GLRoot mGlRoot;
|
||||
private boolean mIsQueued = false;
|
||||
|
||||
public Uploader(GLRoot glRoot) {
|
||||
mGlRoot = glRoot;
|
||||
}
|
||||
|
||||
public synchronized void clear() {
|
||||
mTextures.clear();
|
||||
}
|
||||
|
||||
public synchronized void addTexture(ImageTexture t) {
|
||||
if (t.isReady()) return;
|
||||
mTextures.addLast(t);
|
||||
|
||||
if (mIsQueued) return;
|
||||
mIsQueued = true;
|
||||
mGlRoot.addOnGLIdleListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) {
|
||||
ArrayDeque<ImageTexture> deque = mTextures;
|
||||
synchronized (this) {
|
||||
long now = SystemClock.uptimeMillis();
|
||||
long dueTime = now + UPLOAD_TILE_LIMIT;
|
||||
while (now < dueTime && !deque.isEmpty()) {
|
||||
ImageTexture t = deque.peekFirst();
|
||||
if (t.uploadNextTile(canvas)) {
|
||||
deque.removeFirst();
|
||||
mGlRoot.requestRender();
|
||||
}
|
||||
now = SystemClock.uptimeMillis();
|
||||
}
|
||||
mIsQueued = !mTextures.isEmpty();
|
||||
|
||||
// return true to keep this listener in the queue
|
||||
return mIsQueued;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class Tile extends NativeTexture {
|
||||
|
||||
public int offsetX;
|
||||
public int offsetY;
|
||||
public ImageWrapper image;
|
||||
public Tile nextFreeTile;
|
||||
public int contentWidth;
|
||||
public int contentHeight;
|
||||
public int borderSize;
|
||||
@TileType
|
||||
private int mTileType;
|
||||
|
||||
private static void freeSmallTile(Tile tile) {
|
||||
tile.invalidate();
|
||||
synchronized (sFreeTileLock) {
|
||||
tile.nextFreeTile = sSmallFreeTileHead;
|
||||
sSmallFreeTileHead = tile;
|
||||
}
|
||||
}
|
||||
|
||||
private static void freeLargeTile(Tile tile) {
|
||||
tile.invalidate();
|
||||
synchronized (sFreeTileLock) {
|
||||
tile.nextFreeTile = sLargeFreeTileHead;
|
||||
sLargeFreeTileHead = tile;
|
||||
}
|
||||
}
|
||||
|
||||
public void setSize(@TileType int tileType, int width, int height) {
|
||||
mTileType = tileType;
|
||||
int tileSize;
|
||||
if (tileType == TILE_SMALL) {
|
||||
borderSize = SMALL_BORDER_SIZE;
|
||||
tileSize = SMALL_TILE_SIZE;
|
||||
} else if (tileType == TILE_LARGE) {
|
||||
borderSize = LARGE_BORDER_SIZE;
|
||||
tileSize = LARGE_TILE_SIZE;
|
||||
} else {
|
||||
throw new IllegalStateException("Not support tile type: " + tileType);
|
||||
}
|
||||
contentWidth = width;
|
||||
contentHeight = height;
|
||||
|
||||
mWidth = width + 2 * borderSize;
|
||||
mHeight = height + 2 * borderSize;
|
||||
mTextureWidth = tileSize;
|
||||
mTextureHeight = tileSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void texImage(boolean init) {
|
||||
if (image != null && !image.isRecycled()) {
|
||||
int w, h;
|
||||
if (init) {
|
||||
w = mTextureWidth;
|
||||
h = mTextureHeight;
|
||||
} else {
|
||||
w = mWidth;
|
||||
h = mHeight;
|
||||
}
|
||||
image.texImage(init, offsetX - borderSize, offsetY - borderSize, w, h);
|
||||
}
|
||||
}
|
||||
|
||||
private void invalidate() {
|
||||
invalidateContent();
|
||||
image = null;
|
||||
}
|
||||
|
||||
public void free() {
|
||||
switch (mTileType) {
|
||||
case TILE_SMALL:
|
||||
freeSmallTile(this);
|
||||
break;
|
||||
case TILE_LARGE:
|
||||
freeLargeTile(this);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Not support tile type: " + mTileType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class AnimateRunnable implements Runnable {
|
||||
|
||||
public void doRun() {
|
||||
long lastTime = System.nanoTime();
|
||||
long lastDelay = -1L;
|
||||
|
||||
synchronized (mImage) {
|
||||
// Check released, image busy, Need release
|
||||
if (mReleased.get() || mImage.isImageRecycled() || mImageBusy || mNeedRelease.get()) {
|
||||
mAnimateRunnable = null;
|
||||
return;
|
||||
}
|
||||
// Obtain image
|
||||
mImageBusy = true;
|
||||
}
|
||||
|
||||
synchronized (mImage) {
|
||||
// Release image
|
||||
mImageBusy = false;
|
||||
// Check need release, frameCount <= 1
|
||||
if (mNeedRelease.get() || !mImage.getAnimated()) {
|
||||
mAnimateRunnable = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (mRequestAnimation.get()) {
|
||||
mRunning.lazySet(true);
|
||||
}
|
||||
|
||||
for (; ; ) {
|
||||
// Obtain
|
||||
synchronized (mImage) {
|
||||
// Check released, image busy, Need release, not running
|
||||
if (mReleased.get() || mImage.isImageRecycled() || mImageBusy || mNeedRelease.get() || !mRunning.get()) {
|
||||
mAnimateRunnable = null;
|
||||
return;
|
||||
}
|
||||
// Obtain image
|
||||
mImageBusy = true;
|
||||
}
|
||||
|
||||
mImage.start();
|
||||
long delay = mImage.getDelay();
|
||||
long time = System.nanoTime();
|
||||
if (-1L != lastDelay) {
|
||||
delay -= (time - lastTime) / 1000000 - lastDelay;
|
||||
}
|
||||
lastTime = time;
|
||||
lastDelay = delay;
|
||||
mFrameDirty.lazySet(true);
|
||||
invalidateSelf();
|
||||
|
||||
synchronized (mImage) {
|
||||
// Release image
|
||||
mImageBusy = false;
|
||||
}
|
||||
|
||||
if (delay > 0) {
|
||||
try {
|
||||
Thread.sleep(delay);
|
||||
} catch (InterruptedException e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
doRun();
|
||||
|
||||
while (mNeedRelease.get()) {
|
||||
// Obtain
|
||||
synchronized (mImage) {
|
||||
// Check released
|
||||
if (mReleased.get() || mImage.isImageRecycled()) {
|
||||
break;
|
||||
}
|
||||
// Check image busy
|
||||
if (mImageBusy) {
|
||||
// Image is busy, means it is recycling
|
||||
break;
|
||||
}
|
||||
// Obtain image
|
||||
mImageBusy = true;
|
||||
}
|
||||
|
||||
if (!mReleased.get()) {
|
||||
mImage.release();
|
||||
mReleased.lazySet(true);
|
||||
}
|
||||
|
||||
synchronized (mImage) {
|
||||
// Release image
|
||||
mImageBusy = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.image;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.hippo.image.Image;
|
||||
|
||||
/**
|
||||
* A wrapper for {@link Image}. It is useful for multi-usage.
|
||||
* It handles image recycle automatically.
|
||||
*/
|
||||
public class ImageWrapper {
|
||||
|
||||
private static final String LOG_TAG = "ImageWrapper";
|
||||
|
||||
private final Image mImage;
|
||||
private final Rect mCut;
|
||||
private int mReferences;
|
||||
|
||||
/**
|
||||
* Create ImageWrapper
|
||||
*
|
||||
* @param image the image should not be obtained or recycled.
|
||||
*/
|
||||
public ImageWrapper(@NonNull Image image) {
|
||||
mImage = image;
|
||||
mCut = new Rect(0, 0, image.getWidth(), image.getHeight());
|
||||
}
|
||||
|
||||
/**
|
||||
* Cuts this image to a specified region.
|
||||
* If the region is out of the image size, clamp the region.
|
||||
*/
|
||||
public void setCutRect(int left, int top, int right, int bottom) {
|
||||
mCut.left = Math.max(0, left);
|
||||
mCut.top = Math.max(0, top);
|
||||
mCut.right = Math.min(mImage.getWidth(), right);
|
||||
mCut.bottom = Math.min(mImage.getHeight(), bottom);
|
||||
|
||||
if (mCut.isEmpty()) {
|
||||
// Empty mCut has unspecified behavior
|
||||
Log.e(LOG_TAG, "Cut rect is empty");
|
||||
mCut.set(0, 0, mImage.getWidth(), mImage.getHeight());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cuts this image to a specified region.
|
||||
* The region is described in percent, {@code [0.0f, 1.0f]}.
|
||||
* If the region is out of the image size, clamp the region.
|
||||
*/
|
||||
public void setCutPercent(float left, float top, float right, float bottom) {
|
||||
setCutRect((int) (getWidth() * left), (int) (getHeight() * top),
|
||||
(int) (getWidth() * right), (int) (getHeight() * bottom));
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the image
|
||||
*
|
||||
* @return false for the image is recycled and obtain failed
|
||||
*/
|
||||
public synchronized boolean obtain() {
|
||||
if (mImage.isRecycled()) {
|
||||
return false;
|
||||
} else {
|
||||
++mReferences;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the image
|
||||
*/
|
||||
public synchronized void release() {
|
||||
--mReferences;
|
||||
if (mReferences <= 0 && !mImage.isRecycled()) {
|
||||
mImage.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isImageRecycled() {
|
||||
return mImage.isRecycled();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Image#getAnimated()
|
||||
*/
|
||||
public Boolean getAnimated() {
|
||||
return mImage.getAnimated();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Image#getAnimated()
|
||||
*/
|
||||
public int getWidth() {
|
||||
return mCut.width();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Image#getHeight()
|
||||
*/
|
||||
public int getHeight() {
|
||||
return mCut.height();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Image#texImage(boolean, int, int, int, int)
|
||||
*/
|
||||
public void texImage(boolean init, int offsetX, int offsetY, int width, int height) {
|
||||
mImage.texImage(init, offsetX + mCut.left, offsetY + mCut.top, width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Image#start()
|
||||
*/
|
||||
public void start() {
|
||||
mImage.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Image#getDelay()
|
||||
*/
|
||||
public int getDelay() {
|
||||
return mImage.getDelay();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Image#isOpaque()
|
||||
*/
|
||||
public boolean isOpaque() {
|
||||
return mImage.isOpaque();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Image#isRecycled()
|
||||
*/
|
||||
public boolean isRecycled() {
|
||||
return mImage.isRecycled();
|
||||
}
|
||||
}
|
||||
@@ -1,190 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.util;
|
||||
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.content.ComponentName;
|
||||
import android.hardware.Camera;
|
||||
import android.os.Build;
|
||||
import android.provider.MediaStore.MediaColumns;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
public class ApiHelper {
|
||||
public static final boolean AT_LEAST_16 = Build.VERSION.SDK_INT >= 16;
|
||||
public static final boolean USE_888_PIXEL_FORMAT =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
|
||||
public static final boolean ENABLE_PHOTO_EDITOR =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH;
|
||||
public static final boolean HAS_VIEW_SYSTEM_UI_FLAG_LAYOUT_STABLE =
|
||||
hasField(View.class, "SYSTEM_UI_FLAG_LAYOUT_STABLE");
|
||||
public static final boolean HAS_VIEW_SYSTEM_UI_FLAG_HIDE_NAVIGATION =
|
||||
hasField(View.class, "SYSTEM_UI_FLAG_HIDE_NAVIGATION");
|
||||
public static final boolean HAS_MEDIA_COLUMNS_WIDTH_AND_HEIGHT =
|
||||
hasField(MediaColumns.class, "WIDTH");
|
||||
public static final boolean HAS_REUSING_BITMAP_IN_BITMAP_REGION_DECODER =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
|
||||
public static final boolean HAS_REUSING_BITMAP_IN_BITMAP_FACTORY =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
|
||||
public static final boolean HAS_SET_BEAM_PUSH_URIS =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
|
||||
public static final boolean HAS_SET_DEFALT_BUFFER_SIZE = hasMethod(
|
||||
"android.graphics.SurfaceTexture", "setDefaultBufferSize",
|
||||
int.class, int.class);
|
||||
public static final boolean HAS_RELEASE_SURFACE_TEXTURE = hasMethod(
|
||||
"android.graphics.SurfaceTexture", "release");
|
||||
public static final boolean HAS_SURFACE_TEXTURE =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
|
||||
public static final boolean HAS_MTP =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1;
|
||||
public static final boolean HAS_AUTO_FOCUS_MOVE_CALLBACK =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
|
||||
public static final boolean HAS_REMOTE_VIEWS_SERVICE =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
|
||||
public static final boolean HAS_INTENT_EXTRA_LOCAL_ONLY =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
|
||||
public static final boolean HAS_SET_SYSTEM_UI_VISIBILITY =
|
||||
hasMethod(View.class, "setSystemUiVisibility", int.class);
|
||||
public static final boolean HAS_FACE_DETECTION;
|
||||
public static final boolean HAS_GET_CAMERA_DISABLED =
|
||||
hasMethod(DevicePolicyManager.class, "getCameraDisabled", ComponentName.class);
|
||||
public static final boolean HAS_MEDIA_ACTION_SOUND =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
|
||||
public static final boolean HAS_TIME_LAPSE_RECORDING =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
|
||||
public static final boolean HAS_ZOOM_WHEN_RECORDING =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH;
|
||||
public static final boolean HAS_CAMERA_FOCUS_AREA =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH;
|
||||
public static final boolean HAS_CAMERA_METERING_AREA =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH;
|
||||
public static final boolean HAS_MOTION_EVENT_TRANSFORM =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
|
||||
public static final boolean HAS_EFFECTS_RECORDING = false;
|
||||
// "Background" filter does not have "context" input port in jelly bean.
|
||||
public static final boolean HAS_EFFECTS_RECORDING_CONTEXT_INPUT =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1;
|
||||
public static final boolean HAS_GET_SUPPORTED_VIDEO_SIZE =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
|
||||
public static final boolean HAS_SET_ICON_ATTRIBUTE =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
|
||||
public static final boolean HAS_MEDIA_PROVIDER_FILES_TABLE =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
|
||||
public static final boolean HAS_SURFACE_TEXTURE_RECORDING =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
|
||||
public static final boolean HAS_ACTION_BAR =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
|
||||
// Ex: View.setTranslationX.
|
||||
public static final boolean HAS_VIEW_TRANSFORM_PROPERTIES =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
|
||||
public static final boolean HAS_CAMERA_HDR =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1;
|
||||
public static final boolean HAS_OPTIONS_IN_MUTABLE =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
|
||||
public static final boolean CAN_START_PREVIEW_IN_JPEG_CALLBACK =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH;
|
||||
public static final boolean HAS_VIEW_PROPERTY_ANIMATOR =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1;
|
||||
public static final boolean HAS_POST_ON_ANIMATION =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
|
||||
public static final boolean HAS_ANNOUNCE_FOR_ACCESSIBILITY =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
|
||||
public static final boolean HAS_OBJECT_ANIMATION =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
|
||||
public static final boolean HAS_GLES20_REQUIRED =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
|
||||
public static final boolean HAS_ROTATION_ANIMATION =
|
||||
hasField(WindowManager.LayoutParams.class, "rotationAnimation");
|
||||
public static final boolean HAS_ORIENTATION_LOCK =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR2;
|
||||
public static final boolean HAS_CANCELLATION_SIGNAL =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
|
||||
public static final boolean HAS_MEDIA_MUXER =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR2;
|
||||
public static final boolean HAS_DISPLAY_LISTENER =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1;
|
||||
|
||||
static {
|
||||
boolean hasFaceDetection = false;
|
||||
try {
|
||||
Class<?> listenerClass = Class.forName(
|
||||
"android.hardware.Camera$FaceDetectionListener");
|
||||
hasFaceDetection =
|
||||
hasMethod(Camera.class, "setFaceDetectionListener", listenerClass) &&
|
||||
hasMethod(Camera.class, "startFaceDetection") &&
|
||||
hasMethod(Camera.class, "stopFaceDetection") &&
|
||||
hasMethod(Camera.Parameters.class, "getMaxNumDetectedFaces");
|
||||
} catch (Throwable t) {
|
||||
}
|
||||
HAS_FACE_DETECTION = hasFaceDetection;
|
||||
}
|
||||
|
||||
public static int getIntFieldIfExists(Class<?> klass, String fieldName,
|
||||
Class<?> obj, int defaultVal) {
|
||||
try {
|
||||
Field f = klass.getDeclaredField(fieldName);
|
||||
return f.getInt(obj);
|
||||
} catch (Exception e) {
|
||||
return defaultVal;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean hasField(Class<?> klass, String fieldName) {
|
||||
try {
|
||||
klass.getDeclaredField(fieldName);
|
||||
return true;
|
||||
} catch (NoSuchFieldException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean hasMethod(String className, String methodName,
|
||||
Class<?>... parameterTypes) {
|
||||
try {
|
||||
Class<?> klass = Class.forName(className);
|
||||
klass.getDeclaredMethod(methodName, parameterTypes);
|
||||
return true;
|
||||
} catch (Throwable th) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean hasMethod(
|
||||
Class<?> klass, String methodName, Class<?>... paramTypes) {
|
||||
try {
|
||||
klass.getDeclaredMethod(methodName, paramTypes);
|
||||
return true;
|
||||
} catch (NoSuchMethodException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public interface VERSION_CODES {
|
||||
// These value are copied from Build.VERSION_CODES
|
||||
int GINGERBREAD_MR1 = 10;
|
||||
int HONEYCOMB = 11;
|
||||
int HONEYCOMB_MR1 = 12;
|
||||
int HONEYCOMB_MR2 = 13;
|
||||
int ICE_CREAM_SANDWICH = 14;
|
||||
int ICE_CREAM_SANDWICH_MR1 = 15;
|
||||
int JELLY_BEAN = 16;
|
||||
int JELLY_BEAN_MR1 = 17;
|
||||
int JELLY_BEAN_MR2 = 18;
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.util;
|
||||
|
||||
import android.graphics.Color;
|
||||
|
||||
import com.hippo.yorozuya.AssertError;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
public class GalleryUtils {
|
||||
|
||||
private static final String TAG = "GalleryUtils";
|
||||
private static final List<Integer> sRenderThreads = new CopyOnWriteArrayList<>();
|
||||
|
||||
public static float[] intColorToFloatARGBArray(int from) {
|
||||
return new float[]{
|
||||
Color.alpha(from) / 255f,
|
||||
Color.red(from) / 255f,
|
||||
Color.green(from) / 255f,
|
||||
Color.blue(from) / 255f
|
||||
};
|
||||
}
|
||||
|
||||
// Below are used the detect using database in the render thread. It only
|
||||
// works most of the time, but that's ok because it's for debugging only.
|
||||
|
||||
public static int floatARGBArrayTointColor(float[] from) {
|
||||
return Color.argb((int) (from[0] * 255), (int) (from[1] * 255), (int) (from[2] * 255), (int) (from[3] * 255));
|
||||
}
|
||||
|
||||
public static void setRenderThread() {
|
||||
sRenderThreads.add(Thread.currentThread().hashCode());
|
||||
}
|
||||
|
||||
public static boolean isRenderThread() {
|
||||
return sRenderThreads.contains(Thread.currentThread().hashCode());
|
||||
}
|
||||
|
||||
public static void assertInRenderThread() {
|
||||
if (!isRenderThread()) {
|
||||
throw new AssertError("Should not do this in non-render thread");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.hippo.glview.util;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.graphics.Matrix;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.MotionEvent.PointerCoords;
|
||||
|
||||
public final class MotionEventHelper {
|
||||
private MotionEventHelper() {
|
||||
}
|
||||
|
||||
public static MotionEvent transformEvent(MotionEvent e, Matrix m) {
|
||||
// We try to use the new transform method if possible because it uses
|
||||
// less memory.
|
||||
if (ApiHelper.HAS_MOTION_EVENT_TRANSFORM) {
|
||||
return transformEventNew(e, m);
|
||||
} else {
|
||||
return transformEventOld(e, m);
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
|
||||
private static MotionEvent transformEventNew(MotionEvent e, Matrix m) {
|
||||
MotionEvent newEvent = MotionEvent.obtain(e);
|
||||
newEvent.transform(m);
|
||||
return newEvent;
|
||||
}
|
||||
|
||||
// This is copied from Input.cpp in the android framework.
|
||||
private static MotionEvent transformEventOld(MotionEvent e, Matrix m) {
|
||||
long downTime = e.getDownTime();
|
||||
long eventTime = e.getEventTime();
|
||||
int action = e.getAction();
|
||||
int pointerCount = e.getPointerCount();
|
||||
int[] pointerIds = getPointerIds(e);
|
||||
PointerCoords[] pointerCoords = getPointerCoords(e);
|
||||
int metaState = e.getMetaState();
|
||||
float xPrecision = e.getXPrecision();
|
||||
float yPrecision = e.getYPrecision();
|
||||
int deviceId = e.getDeviceId();
|
||||
int edgeFlags = e.getEdgeFlags();
|
||||
int source = e.getSource();
|
||||
int flags = e.getFlags();
|
||||
|
||||
// Copy the x and y coordinates into an array, map them, and copy back.
|
||||
float[] xy = new float[pointerCoords.length * 2];
|
||||
for (int i = 0; i < pointerCount; i++) {
|
||||
xy[2 * i] = pointerCoords[i].x;
|
||||
xy[2 * i + 1] = pointerCoords[i].y;
|
||||
}
|
||||
m.mapPoints(xy);
|
||||
for (int i = 0; i < pointerCount; i++) {
|
||||
pointerCoords[i].x = xy[2 * i];
|
||||
pointerCoords[i].y = xy[2 * i + 1];
|
||||
pointerCoords[i].orientation = transformAngle(
|
||||
m, pointerCoords[i].orientation);
|
||||
}
|
||||
|
||||
MotionEvent n = MotionEvent.obtain(downTime, eventTime, action,
|
||||
pointerCount, pointerIds, pointerCoords, metaState, xPrecision,
|
||||
yPrecision, deviceId, edgeFlags, source, flags);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
private static int[] getPointerIds(MotionEvent e) {
|
||||
int n = e.getPointerCount();
|
||||
int[] r = new int[n];
|
||||
for (int i = 0; i < n; i++) {
|
||||
r[i] = e.getPointerId(i);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
private static PointerCoords[] getPointerCoords(MotionEvent e) {
|
||||
int n = e.getPointerCount();
|
||||
PointerCoords[] r = new PointerCoords[n];
|
||||
for (int i = 0; i < n; i++) {
|
||||
r[i] = new PointerCoords();
|
||||
e.getPointerCoords(i, r[i]);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
private static float transformAngle(Matrix m, float angleRadians) {
|
||||
// Construct and transform a vector oriented at the specified clockwise
|
||||
// angle from vertical. Coordinate system: down is increasing Y, right is
|
||||
// increasing X.
|
||||
float[] v = new float[2];
|
||||
v[0] = (float) Math.sin(angleRadians);
|
||||
v[1] = (float) -Math.cos(angleRadians);
|
||||
m.mapVectors(v);
|
||||
|
||||
// Derive the transformed vector's clockwise angle from vertical.
|
||||
float result = (float) Math.atan2(v[0], -v[1]);
|
||||
if (result < -Math.PI / 2) {
|
||||
result += Math.PI;
|
||||
} else if (result > Math.PI / 2) {
|
||||
result -= Math.PI;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.view;
|
||||
|
||||
import android.os.SystemClock;
|
||||
|
||||
//
|
||||
// The animation time should ideally be the vsync time the frame will be
|
||||
// displayed, but that is an unknown time in the future. So we use the system
|
||||
// time just after eglSwapBuffers (when GLSurfaceView.onDrawFrame is called)
|
||||
// as a approximation.
|
||||
//
|
||||
public class AnimationTime {
|
||||
private static volatile long sTime;
|
||||
|
||||
// Sets current time as the animation time.
|
||||
public static void update() {
|
||||
sTime = SystemClock.uptimeMillis();
|
||||
}
|
||||
|
||||
// Returns the animation time.
|
||||
public static long get() {
|
||||
return sTime;
|
||||
}
|
||||
|
||||
public static long startTime() {
|
||||
sTime = SystemClock.uptimeMillis();
|
||||
return sTime;
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Matrix;
|
||||
|
||||
import com.hippo.glview.anim.CanvasAnimation;
|
||||
import com.hippo.glview.glrenderer.GLCanvas;
|
||||
|
||||
public interface GLRoot {
|
||||
|
||||
void addOnGLIdleListener(OnGLIdleListener listener);
|
||||
|
||||
void registerLaunchedAnimation(CanvasAnimation animation);
|
||||
|
||||
void requestRenderForced();
|
||||
|
||||
void requestRender();
|
||||
|
||||
void requestLayoutContentPane();
|
||||
|
||||
void lockRenderThread();
|
||||
|
||||
void unlockRenderThread();
|
||||
|
||||
void setContentPane(GLView content);
|
||||
|
||||
void setOrientationSource(OrientationSource source);
|
||||
|
||||
int getDisplayRotation();
|
||||
|
||||
int getCompensation();
|
||||
|
||||
Matrix getCompensationMatrix();
|
||||
|
||||
void freeze();
|
||||
|
||||
void unfreeze();
|
||||
|
||||
void setLightsOutMode(boolean enabled);
|
||||
|
||||
Context getContext();
|
||||
|
||||
int getWidth();
|
||||
|
||||
int getHeight();
|
||||
|
||||
// Listener will be called when GL is idle AND before each frame.
|
||||
// Mainly used for uploading textures.
|
||||
interface OnGLIdleListener {
|
||||
boolean onGLIdle(GLCanvas canvas, boolean renderRequested);
|
||||
}
|
||||
}
|
||||
@@ -1,710 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.opengl.GLSurfaceView;
|
||||
import android.opengl.GLUtils;
|
||||
import android.os.Parcelable;
|
||||
import android.os.Process;
|
||||
import android.os.SystemClock;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.SurfaceHolder;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.hippo.glview.anim.CanvasAnimation;
|
||||
import com.hippo.glview.glrenderer.BasicTexture;
|
||||
import com.hippo.glview.glrenderer.GLCanvas;
|
||||
import com.hippo.glview.glrenderer.GLES11Canvas;
|
||||
import com.hippo.glview.glrenderer.GLES20Canvas;
|
||||
import com.hippo.glview.glrenderer.UploadedTexture;
|
||||
import com.hippo.glview.util.ApiHelper;
|
||||
import com.hippo.glview.util.GalleryUtils;
|
||||
import com.hippo.glview.util.MotionEventHelper;
|
||||
import com.hippo.yorozuya.AssertUtils;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import javax.microedition.khronos.egl.EGL10;
|
||||
import javax.microedition.khronos.egl.EGLConfig;
|
||||
import javax.microedition.khronos.egl.EGLContext;
|
||||
import javax.microedition.khronos.egl.EGLDisplay;
|
||||
import javax.microedition.khronos.opengles.GL10;
|
||||
import javax.microedition.khronos.opengles.GL11;
|
||||
|
||||
// TODO Call attachToRoot and detachFromRoot in render thread
|
||||
// The root component of all <code>GLView</code>s. The rendering is done in GL
|
||||
// thread while the event handling is done in the main thread. To synchronize
|
||||
// the two threads, the entry points of this package need to synchronize on the
|
||||
// <code>GLRootView</code> instance unless it can be proved that the rendering
|
||||
// thread won't access the same thing as the method. The entry points include:
|
||||
// (1) The public methods of HeadUpDisplay
|
||||
// (2) The public methods of CameraHeadUpDisplay
|
||||
// (3) The overridden methods in GLRootView.
|
||||
public class GLRootView extends GLSurfaceView
|
||||
implements GLRoot {
|
||||
private static final String TAG = "GLRootView";
|
||||
|
||||
private static final boolean DEBUG_FPS = false;
|
||||
private static final boolean DEBUG_INVALIDATE = false;
|
||||
private static final boolean DEBUG_DRAWING_STAT = false;
|
||||
private static final int FLAG_INITIALIZED = 1;
|
||||
private static final int FLAG_NEED_LAYOUT = 2;
|
||||
// mCompensationMatrix maps the coordinates of touch events. It is kept sync
|
||||
// with mCompensation.
|
||||
private final Matrix mCompensationMatrix = new Matrix();
|
||||
private final ArrayList<CanvasAnimation> mAnimations =
|
||||
new ArrayList<>();
|
||||
private final ArrayDeque<OnGLIdleListener> mIdleListeners =
|
||||
new ArrayDeque<>();
|
||||
private final IdleRunner mIdleRunner = new IdleRunner();
|
||||
private final ReentrantLock mRenderLock = new ReentrantLock();
|
||||
private final Condition mFreezeCondition =
|
||||
mRenderLock.newCondition();
|
||||
private final Runnable mRequestRenderOnAnimationFrame = this::superRequestRender;
|
||||
private int mFrameCount = 0;
|
||||
private long mFrameCountingStart = 0;
|
||||
private int mInvalidateColor = 0;
|
||||
private GL11 mGL;
|
||||
private GLCanvas mCanvas;
|
||||
private GLView mContentView;
|
||||
private OrientationSource mOrientationSource;
|
||||
// mCompensation is the difference between the UI orientation on GLCanvas
|
||||
// and the framework orientation. See OrientationManager for details.
|
||||
private int mCompensation;
|
||||
private int mDisplayRotation;
|
||||
private int mFlags = FLAG_NEED_LAYOUT;
|
||||
private volatile boolean mRenderRequested = false;
|
||||
private boolean mFreeze;
|
||||
private boolean mInDownState = false;
|
||||
private int mEGLContextClientVersion;
|
||||
|
||||
public GLRootView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public GLRootView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mFlags |= FLAG_INITIALIZED;
|
||||
setBackgroundDrawable(null);
|
||||
|
||||
setEGLConfigChooser(new ConfigChooser());
|
||||
setEGLContextFactory(new ContextFactory());
|
||||
setRenderer(new GLRootRenderer());
|
||||
getHolder().setFormat(PixelFormat.RGB_888);
|
||||
|
||||
// Uncomment this to enable gl error check.
|
||||
// setDebugFlags(DEBUG_CHECK_GL_ERROR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerLaunchedAnimation(CanvasAnimation animation) {
|
||||
// Register the newly launched animation so that we can set the start
|
||||
// time more precisely. (Usually, it takes much longer for first
|
||||
// rendering, so we set the animation start time as the time we
|
||||
// complete rendering)
|
||||
mAnimations.add(animation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addOnGLIdleListener(OnGLIdleListener listener) {
|
||||
synchronized (mIdleListeners) {
|
||||
mIdleListeners.addLast(listener);
|
||||
if (mCanvas != null) {
|
||||
// Wait for onSurfaceCreated
|
||||
mIdleRunner.enable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContentPane(GLView content) {
|
||||
if (mContentView == content) return;
|
||||
if (mContentView != null) {
|
||||
if (mInDownState) {
|
||||
long now = SystemClock.uptimeMillis();
|
||||
MotionEvent cancelEvent = MotionEvent.obtain(
|
||||
now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0);
|
||||
mContentView.dispatchTouchEvent(cancelEvent);
|
||||
cancelEvent.recycle();
|
||||
mInDownState = false;
|
||||
}
|
||||
mContentView.detachFromRoot();
|
||||
BasicTexture.yieldAllTextures();
|
||||
}
|
||||
mContentView = content;
|
||||
if (content != null) {
|
||||
content.attachToRoot(this);
|
||||
requestLayoutContentPane();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestRenderForced() {
|
||||
superRequestRender();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestRender() {
|
||||
if (DEBUG_INVALIDATE) {
|
||||
StackTraceElement e = Thread.currentThread().getStackTrace()[4];
|
||||
String caller = e.getFileName() + ":" + e.getLineNumber() + " ";
|
||||
Log.d(TAG, "invalidate: " + caller);
|
||||
}
|
||||
if (mRenderRequested) return;
|
||||
mRenderRequested = true;
|
||||
postOnAnimation(mRequestRenderOnAnimationFrame);
|
||||
}
|
||||
|
||||
private void superRequestRender() {
|
||||
super.requestRender();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestLayoutContentPane() {
|
||||
mRenderLock.lock();
|
||||
try {
|
||||
if (mContentView == null || (mFlags & FLAG_NEED_LAYOUT) != 0) return;
|
||||
|
||||
// "View" system will invoke onLayout() for initialization(bug ?), we
|
||||
// have to ignore it since the GLThread is not ready yet.
|
||||
if ((mFlags & FLAG_INITIALIZED) == 0) return;
|
||||
|
||||
mFlags |= FLAG_NEED_LAYOUT;
|
||||
requestRender();
|
||||
} finally {
|
||||
mRenderLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private void layoutContentPane() {
|
||||
mFlags &= ~FLAG_NEED_LAYOUT;
|
||||
|
||||
int w = getWidth();
|
||||
int h = getHeight();
|
||||
int displayRotation;
|
||||
int compensation;
|
||||
|
||||
// Get the new orientation values
|
||||
if (mOrientationSource != null) {
|
||||
displayRotation = mOrientationSource.getDisplayRotation();
|
||||
compensation = mOrientationSource.getCompensation();
|
||||
} else {
|
||||
displayRotation = 0;
|
||||
compensation = 0;
|
||||
}
|
||||
|
||||
if (mCompensation != compensation) {
|
||||
mCompensation = compensation;
|
||||
if (mCompensation % 180 != 0) {
|
||||
mCompensationMatrix.setRotate(mCompensation);
|
||||
// move center to origin before rotation
|
||||
mCompensationMatrix.preTranslate(-w / 2, -h / 2);
|
||||
// align with the new origin after rotation
|
||||
mCompensationMatrix.postTranslate(h / 2, w / 2);
|
||||
} else {
|
||||
mCompensationMatrix.setRotate(mCompensation, w / 2, h / 2);
|
||||
}
|
||||
}
|
||||
mDisplayRotation = displayRotation;
|
||||
|
||||
// Do the actual layout.
|
||||
if (mCompensation % 180 != 0) {
|
||||
int tmp = w;
|
||||
w = h;
|
||||
h = tmp;
|
||||
}
|
||||
Log.i(TAG, "layout content pane " + w + "x" + h + " (compensation " + mCompensation + ")");
|
||||
if (mContentView != null && w != 0 && h != 0) {
|
||||
mContentView.measure(GLView.MeasureSpec.makeMeasureSpec(w, GLView.MeasureSpec.EXACTLY),
|
||||
GLView.MeasureSpec.makeMeasureSpec(h, GLView.MeasureSpec.EXACTLY));
|
||||
mContentView.layout(0, 0, w, h);
|
||||
}
|
||||
// Uncomment this to dump the view hierarchy.
|
||||
//mContentView.dumpTree("");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(
|
||||
boolean changed, int left, int top, int right, int bottom) {
|
||||
if (changed) requestLayoutContentPane();
|
||||
}
|
||||
|
||||
private void outputFps() {
|
||||
long now = System.nanoTime();
|
||||
if (mFrameCountingStart == 0) {
|
||||
mFrameCountingStart = now;
|
||||
} else if ((now - mFrameCountingStart) > 1000000000) {
|
||||
Log.d(TAG, "fps: " + (double) mFrameCount
|
||||
* 1000000000 / (now - mFrameCountingStart));
|
||||
mFrameCountingStart = now;
|
||||
mFrameCount = 0;
|
||||
}
|
||||
++mFrameCount;
|
||||
}
|
||||
|
||||
private void onDrawFrameLocked() {
|
||||
if (DEBUG_FPS) outputFps();
|
||||
// release the unbound textures and deleted buffers.
|
||||
mCanvas.deleteRecycledResources();
|
||||
|
||||
// reset texture upload limit
|
||||
UploadedTexture.resetUploadLimit();
|
||||
|
||||
mRenderRequested = false;
|
||||
|
||||
if ((mOrientationSource != null
|
||||
&& mDisplayRotation != mOrientationSource.getDisplayRotation())
|
||||
|| (mFlags & FLAG_NEED_LAYOUT) != 0) {
|
||||
layoutContentPane();
|
||||
}
|
||||
|
||||
mCanvas.save(GLCanvas.SAVE_FLAG_ALL);
|
||||
rotateCanvas(-mCompensation);
|
||||
if (mContentView != null) {
|
||||
mContentView.render(mCanvas);
|
||||
} else {
|
||||
// Make sure we always draw something to prevent displaying garbage
|
||||
mCanvas.clearBuffer();
|
||||
}
|
||||
mCanvas.restore();
|
||||
|
||||
if (!mAnimations.isEmpty()) {
|
||||
long now = AnimationTime.get();
|
||||
for (int i = 0, n = mAnimations.size(); i < n; i++) {
|
||||
mAnimations.get(i).setStartTime(now);
|
||||
}
|
||||
mAnimations.clear();
|
||||
}
|
||||
|
||||
if (UploadedTexture.uploadLimitReached()) {
|
||||
requestRender();
|
||||
}
|
||||
|
||||
synchronized (mIdleListeners) {
|
||||
if (!mIdleListeners.isEmpty()) mIdleRunner.enable();
|
||||
}
|
||||
|
||||
if (DEBUG_INVALIDATE) {
|
||||
mCanvas.fillRect(10, 10, 5, 5, mInvalidateColor);
|
||||
mInvalidateColor = ~mInvalidateColor;
|
||||
}
|
||||
|
||||
if (DEBUG_DRAWING_STAT) {
|
||||
mCanvas.dumpStatisticsAndClear();
|
||||
}
|
||||
}
|
||||
|
||||
private void rotateCanvas(int degrees) {
|
||||
if (degrees == 0) return;
|
||||
int w = getWidth();
|
||||
int h = getHeight();
|
||||
int cx = w / 2;
|
||||
int cy = h / 2;
|
||||
mCanvas.translate(cx, cy);
|
||||
mCanvas.rotate(degrees, 0, 0, 1);
|
||||
if (degrees % 180 != 0) {
|
||||
mCanvas.translate(-cy, -cx);
|
||||
} else {
|
||||
mCanvas.translate(-cx, -cy);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchTouchEvent(MotionEvent event) {
|
||||
if (!isEnabled()) return false;
|
||||
|
||||
int action = event.getAction();
|
||||
if (action == MotionEvent.ACTION_CANCEL
|
||||
|| action == MotionEvent.ACTION_UP) {
|
||||
mInDownState = false;
|
||||
} else if (!mInDownState && action != MotionEvent.ACTION_DOWN) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mCompensation != 0) {
|
||||
event = MotionEventHelper.transformEvent(event, mCompensationMatrix);
|
||||
}
|
||||
|
||||
mRenderLock.lock();
|
||||
try {
|
||||
// If this has been detached from root, we don't need to handle event
|
||||
boolean handled = mContentView != null
|
||||
&& mContentView.dispatchTouchEvent(event);
|
||||
if (action == MotionEvent.ACTION_DOWN && handled) {
|
||||
mInDownState = true;
|
||||
}
|
||||
return handled;
|
||||
} finally {
|
||||
mRenderLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lockRenderThread() {
|
||||
mRenderLock.lock();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unlockRenderThread() {
|
||||
mRenderLock.unlock();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
unfreeze();
|
||||
if (mContentView != null) {
|
||||
mContentView.pause();
|
||||
}
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
if (mContentView != null) {
|
||||
mContentView.resume();
|
||||
}
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOrientationSource(OrientationSource source) {
|
||||
mOrientationSource = source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDisplayRotation() {
|
||||
return mDisplayRotation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCompensation() {
|
||||
return mCompensation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Matrix getCompensationMatrix() {
|
||||
return mCompensationMatrix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void freeze() {
|
||||
mRenderLock.lock();
|
||||
mFreeze = true;
|
||||
mRenderLock.unlock();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unfreeze() {
|
||||
mRenderLock.lock();
|
||||
mFreeze = false;
|
||||
mFreezeCondition.signalAll();
|
||||
mRenderLock.unlock();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLightsOutMode(boolean enabled) {
|
||||
if (!ApiHelper.HAS_SET_SYSTEM_UI_VISIBILITY) return;
|
||||
|
||||
int flags = 0;
|
||||
if (enabled) {
|
||||
flags = STATUS_BAR_HIDDEN;
|
||||
if (ApiHelper.HAS_VIEW_SYSTEM_UI_FLAG_LAYOUT_STABLE) {
|
||||
flags |= (SYSTEM_UI_FLAG_FULLSCREEN | SYSTEM_UI_FLAG_LAYOUT_STABLE);
|
||||
}
|
||||
}
|
||||
setSystemUiVisibility(flags);
|
||||
}
|
||||
|
||||
// We need to unfreeze in the following methods and in onPause().
|
||||
// These methods will wait on GLThread. If we have freezed the GLRootView,
|
||||
// the GLThread will wait on main thread to call unfreeze and cause dead
|
||||
// lock.
|
||||
@Override
|
||||
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
|
||||
unfreeze();
|
||||
super.surfaceChanged(holder, format, w, h);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
unfreeze();
|
||||
super.surfaceCreated(holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
unfreeze();
|
||||
super.surfaceDestroyed(holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
unfreeze();
|
||||
super.onDetachedFromWindow();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected void dispatchSaveInstanceState(@NonNull SparseArray<Parcelable> container) {
|
||||
super.dispatchSaveInstanceState(container);
|
||||
if (mContentView != null) {
|
||||
mContentView.saveHierarchyState(container);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected void dispatchRestoreInstanceState(@NonNull SparseArray<Parcelable> container) {
|
||||
super.dispatchRestoreInstanceState(container);
|
||||
if (mContentView != null) {
|
||||
mContentView.restoreHierarchyState(container);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
try {
|
||||
unfreeze();
|
||||
} finally {
|
||||
super.finalize();
|
||||
}
|
||||
}
|
||||
|
||||
private class GLRootRenderer implements GLSurfaceView.Renderer {
|
||||
|
||||
/**
|
||||
* Called when the context is created, possibly after automatic destruction.
|
||||
*/
|
||||
@Override
|
||||
public void onSurfaceCreated(GL10 gl1, EGLConfig config) {
|
||||
GL11 gl = (GL11) gl1;
|
||||
if (mGL != null) {
|
||||
// The GL Object has changed
|
||||
Log.i(TAG, "GLObject has changed from " + mGL + " to " + gl);
|
||||
}
|
||||
mRenderLock.lock();
|
||||
try {
|
||||
mGL = gl;
|
||||
mCanvas = mEGLContextClientVersion == 2 ? new GLES20Canvas() : new GLES11Canvas(gl);
|
||||
BasicTexture.invalidateAllTextures();
|
||||
} finally {
|
||||
mRenderLock.unlock();
|
||||
}
|
||||
|
||||
if (DEBUG_FPS) {
|
||||
setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
|
||||
} else {
|
||||
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the OpenGL surface is recreated without destroying the
|
||||
* context.
|
||||
*/
|
||||
@Override
|
||||
public void onSurfaceChanged(GL10 gl1, int width, int height) {
|
||||
Log.i(TAG, "onSurfaceChanged: " + width + "x" + height + ", gl10: " + gl1.toString());
|
||||
Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY);
|
||||
GalleryUtils.setRenderThread();
|
||||
GL11 gl = (GL11) gl1;
|
||||
AssertUtils.assertTrue(mGL == gl);
|
||||
|
||||
mCanvas.setSize(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDrawFrame(GL10 gl) {
|
||||
AnimationTime.update();
|
||||
|
||||
long t0 = System.nanoTime();
|
||||
|
||||
mRenderLock.lock();
|
||||
|
||||
while (mFreeze) {
|
||||
mFreezeCondition.awaitUninterruptibly();
|
||||
}
|
||||
|
||||
try {
|
||||
onDrawFrameLocked();
|
||||
} finally {
|
||||
mRenderLock.unlock();
|
||||
}
|
||||
|
||||
long t = System.nanoTime();
|
||||
long duration = (t - t0) / 1000000;
|
||||
|
||||
if (duration > 8) {
|
||||
Log.v(TAG, "--- " + duration + " ---");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class IdleRunner implements Runnable {
|
||||
// true if the idle runner is in the queue
|
||||
private boolean mActive = false;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
OnGLIdleListener listener;
|
||||
synchronized (mIdleListeners) {
|
||||
mActive = false;
|
||||
if (mIdleListeners.isEmpty()) return;
|
||||
listener = mIdleListeners.removeFirst();
|
||||
}
|
||||
mRenderLock.lock();
|
||||
boolean keepInQueue;
|
||||
try {
|
||||
keepInQueue = listener.onGLIdle(mCanvas, mRenderRequested);
|
||||
} finally {
|
||||
mRenderLock.unlock();
|
||||
}
|
||||
synchronized (mIdleListeners) {
|
||||
if (keepInQueue) mIdleListeners.addLast(listener);
|
||||
if (!mRenderRequested && !mIdleListeners.isEmpty()) enable();
|
||||
}
|
||||
}
|
||||
|
||||
public void enable() {
|
||||
// Who gets the flag can add it to the queue
|
||||
if (mActive) return;
|
||||
mActive = true;
|
||||
queueEvent(this);
|
||||
}
|
||||
}
|
||||
|
||||
// Always chose a config
|
||||
private class ConfigChooser implements EGLConfigChooser {
|
||||
|
||||
private final int[] mValue = new int[1];
|
||||
|
||||
@Override
|
||||
public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
|
||||
int[] num_config = new int[1];
|
||||
int[] configSpec = new int[]{EGL10.EGL_NONE};
|
||||
if (!egl.eglChooseConfig(display, configSpec, null, 0, num_config)) {
|
||||
throw new IllegalArgumentException("eglChooseConfig failed");
|
||||
}
|
||||
|
||||
int numConfigs = num_config[0];
|
||||
|
||||
if (numConfigs <= 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"No configs match configSpec");
|
||||
}
|
||||
|
||||
EGLConfig[] configs = new EGLConfig[numConfigs];
|
||||
if (!egl.eglChooseConfig(display, configSpec, configs, numConfigs, num_config)) {
|
||||
throw new IllegalArgumentException("eglChooseConfig#2 failed");
|
||||
}
|
||||
EGLConfig config = chooseConfig(egl, display, configs);
|
||||
if (config == null) {
|
||||
throw new IllegalArgumentException("No config chosen");
|
||||
}
|
||||
|
||||
int renderableType = findConfigAttrib(egl, display, config, EGL10.EGL_RENDERABLE_TYPE);
|
||||
if ((renderableType & 0x0004) == 0x0004 /* EGL_OPENGL_ES2_BIT */) {
|
||||
mEGLContextClientVersion = 2;
|
||||
} else {
|
||||
mEGLContextClientVersion = 1;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
private EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs) {
|
||||
// Use score to avoid "No config chosen"
|
||||
int configIndex = 0;
|
||||
int maxScore = 0;
|
||||
|
||||
for (int i = 0, n = configs.length; i < n; i++) {
|
||||
final EGLConfig config = configs[i];
|
||||
final int redSize = findConfigAttrib(egl, display, config,
|
||||
EGL10.EGL_RED_SIZE);
|
||||
final int greenSize = findConfigAttrib(egl, display, config,
|
||||
EGL10.EGL_GREEN_SIZE);
|
||||
final int blueSize = findConfigAttrib(egl, display, config,
|
||||
EGL10.EGL_BLUE_SIZE);
|
||||
final int alphaSize = findConfigAttrib(egl, display, config,
|
||||
EGL10.EGL_ALPHA_SIZE);
|
||||
final int sampleBuffers = findConfigAttrib(egl, display, config,
|
||||
EGL10.EGL_SAMPLE_BUFFERS);
|
||||
final int samples = findConfigAttrib(egl, display, config,
|
||||
EGL10.EGL_SAMPLES);
|
||||
final int depth = findConfigAttrib(egl, display, config,
|
||||
EGL10.EGL_DEPTH_SIZE);
|
||||
final int stencil = findConfigAttrib(egl, display, config,
|
||||
EGL10.EGL_STENCIL_SIZE);
|
||||
|
||||
final int score = redSize + greenSize + blueSize + alphaSize +
|
||||
sampleBuffers + samples - depth - stencil;
|
||||
|
||||
if (score > maxScore) {
|
||||
maxScore = score;
|
||||
configIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
return configs[configIndex];
|
||||
}
|
||||
|
||||
private int findConfigAttrib(EGL10 egl, EGLDisplay display,
|
||||
EGLConfig config, int attribute) {
|
||||
if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) {
|
||||
return mValue[0];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private class ContextFactory implements EGLContextFactory {
|
||||
|
||||
private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
|
||||
|
||||
@Override
|
||||
public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig config) {
|
||||
int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, mEGLContextClientVersion,
|
||||
EGL10.EGL_NONE};
|
||||
|
||||
return egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT,
|
||||
mEGLContextClientVersion != 0 ? attrib_list : null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context) {
|
||||
if (!egl.eglDestroyContext(display, context)) {
|
||||
Log.e("DefaultContextFactory", "display:" + display + " context: " + context);
|
||||
throw new RuntimeException("eglDestroyContex failed: " + GLUtils.getEGLErrorString(egl.eglGetError()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,98 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 Tarsin Norbin
|
||||
*
|
||||
* This file is part of EhViewer
|
||||
*
|
||||
* EhViewer is free software: you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* EhViewer is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with EhViewer.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.view;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
public class Gravity {
|
||||
|
||||
public static final int NO_GRAVITY = 0x0000;
|
||||
public static final int LEFT = 0x0001;
|
||||
public static final int TOP = 0x0002;
|
||||
public static final int RIGHT = 0x0004;
|
||||
public static final int BOTTOM = 0x0008;
|
||||
public static final int CENTER_HORIZONTAL = LEFT | RIGHT;
|
||||
public static final int HORIZONTAL_MASK = CENTER_HORIZONTAL;
|
||||
public static final int CENTER_VERTICAL = TOP | BOTTOM;
|
||||
public static final int CENTER = CENTER_VERTICAL | CENTER_HORIZONTAL;
|
||||
public static final int VERTICAL_MASK = CENTER_VERTICAL;
|
||||
public static final int POSITION_BEGIN = 0;
|
||||
public static final int POSITION_CENTER = 1;
|
||||
public static final int POSITION_FINISH = 2;
|
||||
public static final int HORIZONTAL = 0;
|
||||
public static final int VERTICAL = 1;
|
||||
|
||||
public static boolean centerHorizontal(int gravity) {
|
||||
return (gravity & HORIZONTAL_MASK) == CENTER_HORIZONTAL;
|
||||
}
|
||||
|
||||
public static boolean right(int gravity) {
|
||||
return (gravity & HORIZONTAL_MASK) == RIGHT;
|
||||
}
|
||||
|
||||
public static boolean left(int gravity) {
|
||||
return (gravity & HORIZONTAL_MASK) == LEFT;
|
||||
}
|
||||
|
||||
public static boolean centerVertical(int gravity) {
|
||||
return (gravity & VERTICAL_MASK) == CENTER_VERTICAL;
|
||||
}
|
||||
|
||||
public static boolean top(int gravity) {
|
||||
return (gravity & VERTICAL_MASK) == TOP;
|
||||
}
|
||||
|
||||
public static boolean bottom(int gravity) {
|
||||
return (gravity & VERTICAL_MASK) == BOTTOM;
|
||||
}
|
||||
|
||||
public static @PositionMode
|
||||
int getPosition(int gravity, @DirectionMode int direction) {
|
||||
if (direction == HORIZONTAL) {
|
||||
if (right(gravity)) {
|
||||
return POSITION_FINISH;
|
||||
} else if (centerHorizontal(gravity)) {
|
||||
return POSITION_CENTER;
|
||||
} else {
|
||||
return POSITION_BEGIN;
|
||||
}
|
||||
} else {
|
||||
if (bottom(gravity)) {
|
||||
return POSITION_FINISH;
|
||||
} else if (centerVertical(gravity)) {
|
||||
return POSITION_CENTER;
|
||||
} else {
|
||||
return POSITION_BEGIN;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@IntDef({POSITION_BEGIN, POSITION_CENTER, POSITION_FINISH})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface PositionMode {
|
||||
}
|
||||
|
||||
@IntDef({HORIZONTAL, VERTICAL})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface DirectionMode {
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.view;
|
||||
|
||||
public interface OrientationSource {
|
||||
int getDisplayRotation();
|
||||
|
||||
int getCompensation();
|
||||
}
|
||||
@@ -1,240 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ViewConfiguration;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.hippo.yorozuya.LayoutUtils;
|
||||
import com.hippo.yorozuya.SimpleHandler;
|
||||
|
||||
public class TouchHelper {
|
||||
|
||||
/**
|
||||
* Cache the touch slop from the context that created the view.
|
||||
*/
|
||||
private static int sTouchSlop = 8;
|
||||
private final TouchOwner mOwner;
|
||||
private boolean mPrePressed = false;
|
||||
private boolean mHasPerformedLongPress = false;
|
||||
private CheckForLongPress mPendingCheckForLongPress = null;
|
||||
private CheckForTap mPendingCheckForTap = null;
|
||||
private PerformClick mPerformClick = null;
|
||||
private UnsetPressedState mUnsetPressedState = null;
|
||||
|
||||
public TouchHelper(@NonNull TouchOwner owner) {
|
||||
mOwner = owner;
|
||||
}
|
||||
|
||||
public static void initialize(Context context) {
|
||||
sTouchSlop = LayoutUtils.dp2pix(context, 8); // 8dp
|
||||
}
|
||||
|
||||
public boolean onTouch(MotionEvent event) {
|
||||
TouchOwner owner = mOwner;
|
||||
final float x = event.getX();
|
||||
final float y = event.getY();
|
||||
|
||||
if (event.getAction() == MotionEvent.ACTION_DOWN ||
|
||||
event.getAction() == MotionEvent.ACTION_MOVE)
|
||||
owner.setHotspot(x, y);
|
||||
|
||||
if (!owner.isEnabled()) {
|
||||
if (event.getAction() == MotionEvent.ACTION_UP && owner.isPressed()) {
|
||||
owner.setPressed(false);
|
||||
}
|
||||
// A disabled view that is clickable still consumes the touch
|
||||
// events, it just doesn't respond to them.
|
||||
return (owner.isClickable() || owner.isLongClickable());
|
||||
}
|
||||
|
||||
if (owner.isClickable() || owner.isLongClickable()) {
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_UP:
|
||||
if (owner.isPressed() || mPrePressed) {
|
||||
if (mPrePressed) {
|
||||
// The button is being released before we actually
|
||||
// showed it as pressed. Make it show the pressed
|
||||
// state now (before scheduling the click) to ensure
|
||||
// the user sees it.
|
||||
setPressed(true, x, y);
|
||||
}
|
||||
|
||||
if (!mHasPerformedLongPress) {
|
||||
// This is a tap, so remove the longpress check
|
||||
removeLongPressCallback();
|
||||
|
||||
// Use a Runnable and post this rather than calling
|
||||
// performClick directly. This lets other visual state
|
||||
// of the view update before click actions start.
|
||||
if (mPerformClick == null) {
|
||||
mPerformClick = new PerformClick();
|
||||
}
|
||||
if (!SimpleHandler.getInstance().post(mPerformClick)) {
|
||||
owner.performClick();
|
||||
}
|
||||
}
|
||||
|
||||
if (mUnsetPressedState == null) {
|
||||
mUnsetPressedState = new UnsetPressedState();
|
||||
}
|
||||
|
||||
if (mPrePressed) {
|
||||
SimpleHandler.getInstance().postDelayed(mUnsetPressedState,
|
||||
ViewConfiguration.getPressedStateDuration());
|
||||
} else if (!SimpleHandler.getInstance().post(mUnsetPressedState)) {
|
||||
// If the post failed, unpress right now
|
||||
mUnsetPressedState.run();
|
||||
}
|
||||
|
||||
removeTapCallback();
|
||||
}
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
mHasPerformedLongPress = false;
|
||||
|
||||
mPrePressed = true;
|
||||
if (mPendingCheckForTap == null) {
|
||||
mPendingCheckForTap = new CheckForTap();
|
||||
}
|
||||
mPendingCheckForTap.x = event.getX();
|
||||
mPendingCheckForTap.y = event.getY();
|
||||
SimpleHandler.getInstance().postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
owner.setPressed(false);
|
||||
removeTapCallback();
|
||||
removeLongPressCallback();
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
owner.setHotspot(x, y);
|
||||
|
||||
// Be lenient about moving outside of buttons
|
||||
if (!pointInView(x, y, sTouchSlop)) {
|
||||
// Outside button
|
||||
removeTapCallback();
|
||||
if (owner.isPressed()) {
|
||||
// Remove any future long press/tap checks
|
||||
removeLongPressCallback();
|
||||
|
||||
owner.setPressed(false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void setPressed(boolean pressed, float x, float y) {
|
||||
mOwner.setPressed(pressed);
|
||||
if (pressed) {
|
||||
mOwner.setHotspot(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to determine whether the given point, in local coordinates,
|
||||
* is inside the view, where the area of the view is expanded by the slop factor.
|
||||
* This method is called while processing touch-move events to determine if the event
|
||||
* is still within the view.
|
||||
*/
|
||||
public boolean pointInView(float localX, float localY, float slop) {
|
||||
return localX >= -slop && localY >= -slop && localX < (mOwner.getWidth() + slop) &&
|
||||
localY < (mOwner.getHeight() + slop);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the longpress detection timer.
|
||||
*/
|
||||
private void removeLongPressCallback() {
|
||||
if (mPendingCheckForLongPress != null) {
|
||||
SimpleHandler.getInstance().removeCallbacks(mPendingCheckForLongPress);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the tap detection timer.
|
||||
*/
|
||||
private void removeTapCallback() {
|
||||
if (mPendingCheckForTap != null) {
|
||||
mPrePressed = false;
|
||||
SimpleHandler.getInstance().removeCallbacks(mPendingCheckForTap);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkForLongClick(int delayOffset) {
|
||||
if (mOwner.isLongClickable()) {
|
||||
mHasPerformedLongPress = false;
|
||||
|
||||
if (mPendingCheckForLongPress == null) {
|
||||
mPendingCheckForLongPress = new CheckForLongPress();
|
||||
}
|
||||
SimpleHandler.getInstance().postDelayed(mPendingCheckForLongPress,
|
||||
ViewConfiguration.getLongPressTimeout() - delayOffset);
|
||||
}
|
||||
}
|
||||
|
||||
private final class CheckForLongPress implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (mOwner.isPressed()) {
|
||||
if (mOwner.performLongClick()) {
|
||||
mHasPerformedLongPress = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class CheckForTap implements Runnable {
|
||||
public float x;
|
||||
public float y;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
mPrePressed = false;
|
||||
setPressed(true, x, y);
|
||||
checkForLongClick(ViewConfiguration.getTapTimeout());
|
||||
}
|
||||
}
|
||||
|
||||
private final class PerformClick implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
mOwner.performClick();
|
||||
}
|
||||
}
|
||||
|
||||
private final class UnsetPressedState implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
mOwner.setPressed(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.view;
|
||||
|
||||
public interface TouchOwner {
|
||||
|
||||
void setHotspot(float x, float y);
|
||||
|
||||
boolean isEnabled();
|
||||
|
||||
boolean isPressed();
|
||||
|
||||
void setPressed(boolean pressed);
|
||||
|
||||
boolean isClickable();
|
||||
|
||||
boolean isLongClickable();
|
||||
|
||||
boolean performClick();
|
||||
|
||||
boolean performLongClick();
|
||||
|
||||
int getWidth();
|
||||
|
||||
int getHeight();
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.widget;
|
||||
|
||||
import com.hippo.glview.view.GLView;
|
||||
import com.hippo.glview.view.Gravity;
|
||||
|
||||
public class GLFrameLayout extends GLView {
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthSpec, int heightSpec) {
|
||||
int maxWidth = 0;
|
||||
int maxHeight = 0;
|
||||
for (int i = 0, n = getComponentCount(); i < n; i++) {
|
||||
GLView component = getComponent(i);
|
||||
if (component.getVisibility() == GONE) {
|
||||
continue;
|
||||
}
|
||||
measureComponent(component, widthSpec, heightSpec);
|
||||
maxWidth = Math.max(maxWidth, component.getMeasuredWidth());
|
||||
maxHeight = Math.max(maxHeight, component.getMeasuredHeight());
|
||||
}
|
||||
|
||||
// Consider min
|
||||
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
|
||||
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
|
||||
|
||||
// The final
|
||||
maxWidth = getDefaultSize(maxWidth, widthSpec);
|
||||
maxHeight = getDefaultSize(maxHeight, heightSpec);
|
||||
|
||||
setMeasuredSize(maxWidth, maxHeight);
|
||||
|
||||
if (MeasureSpec.getSize(widthSpec) != MeasureSpec.EXACTLY ||
|
||||
MeasureSpec.getSize(heightSpec) != MeasureSpec.EXACTLY) {
|
||||
// Measure again
|
||||
measureAllComponents(this, MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.EXACTLY));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changeSize, int left, int top, int right, int bottom) {
|
||||
int width = getWidth();
|
||||
int height = getHeight();
|
||||
|
||||
for (int i = 0, n = getComponentCount(); i < n; i++) {
|
||||
GLView component = getComponent(i);
|
||||
if (component.getVisibility() == GONE) {
|
||||
continue;
|
||||
}
|
||||
int measureWidth = component.getMeasuredWidth();
|
||||
int measureHeight = component.getMeasuredHeight();
|
||||
int componentLeft;
|
||||
int componentTop;
|
||||
|
||||
GravityLayoutParams lp = (GravityLayoutParams) component.getLayoutParams();
|
||||
int gravity = lp.gravity;
|
||||
if (Gravity.centerHorizontal(gravity)) {
|
||||
componentLeft = (width / 2) - (measureWidth / 2);
|
||||
} else if (Gravity.right(gravity)) {
|
||||
componentLeft = width - measureWidth;
|
||||
} else {
|
||||
componentLeft = 0;
|
||||
}
|
||||
if (Gravity.centerVertical(gravity)) {
|
||||
componentTop = (height / 2) - (measureHeight / 2);
|
||||
} else if (Gravity.bottom(gravity)) {
|
||||
componentTop = height - measureHeight;
|
||||
} else {
|
||||
componentTop = 0;
|
||||
}
|
||||
|
||||
component.layout(componentLeft, componentTop,
|
||||
componentLeft + measureWidth, componentTop + measureHeight);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean checkLayoutParams(GLView.LayoutParams p) {
|
||||
return p instanceof GravityLayoutParams;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LayoutParams generateDefaultLayoutParams() {
|
||||
return new GravityLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LayoutParams generateLayoutParams(GLView.LayoutParams p) {
|
||||
return p == null ? generateDefaultLayoutParams() : new GravityLayoutParams(p);
|
||||
}
|
||||
}
|
||||
@@ -1,243 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.widget;
|
||||
|
||||
import android.graphics.Rect;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
|
||||
import com.hippo.glview.view.GLView;
|
||||
import com.hippo.glview.view.Gravity;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class GLLinearLayout extends GLView {
|
||||
|
||||
public static final int HORIZONTAL = 0;
|
||||
public static final int VERTICAL = 1;
|
||||
private final List<GLView> mTempList = new ArrayList<>();
|
||||
private int mInterval = 0;
|
||||
private int mOrientation = VERTICAL;
|
||||
|
||||
public void setInterval(int interval) {
|
||||
if (mInterval != interval) {
|
||||
mInterval = interval;
|
||||
requestLayout();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the layout be a column or a row.
|
||||
*
|
||||
* @param orientation Pass {@link #HORIZONTAL} or {@link #VERTICAL}. Default
|
||||
* value is {@link #HORIZONTAL}.
|
||||
*/
|
||||
public void setOrientation(@OrientationMode int orientation) {
|
||||
if (mOrientation != orientation) {
|
||||
mOrientation = orientation;
|
||||
requestLayout();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthSpec, int heightSpec) {
|
||||
int layoutWidthSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthSpec) - mPaddings.left - mPaddings.right,
|
||||
MeasureSpec.getMode(widthSpec));
|
||||
int layoutHeightSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightSpec) - mPaddings.top - mPaddings.bottom,
|
||||
MeasureSpec.getMode(heightSpec));
|
||||
|
||||
if (mOrientation == HORIZONTAL && MeasureSpec.getMode(widthSpec) == MeasureSpec.EXACTLY) {
|
||||
int width = MeasureSpec.getSize(layoutWidthSpec);
|
||||
float sumWeight = 0.0f;
|
||||
|
||||
for (int i = 0, n = getComponentCount(); i < n; i++) {
|
||||
final GLView component = getComponent(i);
|
||||
if (component.getVisibility() == GONE) {
|
||||
continue;
|
||||
}
|
||||
final LayoutParams lp = (LayoutParams) component.getLayoutParams();
|
||||
|
||||
if (lp.weight > 0.0f) {
|
||||
sumWeight += lp.weight;
|
||||
mTempList.add(component);
|
||||
} else {
|
||||
measureComponent(component, layoutWidthSpec, layoutHeightSpec);
|
||||
width -= component.getMeasuredWidth();
|
||||
}
|
||||
}
|
||||
|
||||
for (GLView component : mTempList) {
|
||||
final LayoutParams lp = (LayoutParams) component.getLayoutParams();
|
||||
final int componentWidthSpec = MeasureSpec.makeMeasureSpec(
|
||||
(int) (width * lp.weight / sumWeight), MeasureSpec.EXACTLY);
|
||||
final int componentHeightSpec = getComponentSpec(layoutHeightSpec, lp.height);
|
||||
component.measure(componentWidthSpec, componentHeightSpec);
|
||||
}
|
||||
|
||||
mTempList.clear();
|
||||
} else if (mOrientation == VERTICAL && MeasureSpec.getMode(heightSpec) == MeasureSpec.EXACTLY) {
|
||||
int height = MeasureSpec.getSize(layoutHeightSpec);
|
||||
float sumWeight = 0.0f;
|
||||
|
||||
for (int i = 0, n = getComponentCount(); i < n; i++) {
|
||||
final GLView component = getComponent(i);
|
||||
if (component.getVisibility() == GONE) {
|
||||
continue;
|
||||
}
|
||||
final LayoutParams lp = (LayoutParams) component.getLayoutParams();
|
||||
|
||||
if (lp.weight > 0.0f) {
|
||||
sumWeight += lp.weight;
|
||||
mTempList.add(component);
|
||||
} else {
|
||||
measureComponent(component, layoutWidthSpec, layoutHeightSpec);
|
||||
height -= component.getMeasuredHeight();
|
||||
}
|
||||
}
|
||||
|
||||
for (GLView component : mTempList) {
|
||||
final LayoutParams lp = (LayoutParams) component.getLayoutParams();
|
||||
final int componentWidthSpec = getComponentSpec(layoutWidthSpec, lp.width);
|
||||
final int componentHeightSpec = MeasureSpec.makeMeasureSpec(
|
||||
(int) (height * lp.weight / sumWeight), MeasureSpec.EXACTLY);
|
||||
component.measure(componentWidthSpec, componentHeightSpec);
|
||||
}
|
||||
|
||||
mTempList.clear();
|
||||
} else {
|
||||
measureAllComponents(this, layoutWidthSpec, layoutHeightSpec);
|
||||
}
|
||||
|
||||
int maxWidth = 0;
|
||||
int maxHeight = 0;
|
||||
for (int i = 0, n = getComponentCount(); i < n; i++) {
|
||||
GLView component = getComponent(i);
|
||||
if (component.getVisibility() == GONE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mOrientation == VERTICAL) {
|
||||
maxWidth = Math.max(maxWidth, component.getMeasuredWidth());
|
||||
if (i != 0) {
|
||||
maxHeight += mInterval;
|
||||
}
|
||||
maxHeight += component.getMeasuredHeight();
|
||||
} else if (mOrientation == HORIZONTAL) {
|
||||
maxHeight = Math.max(maxHeight, component.getMeasuredHeight());
|
||||
if (i != 0) {
|
||||
maxWidth += mInterval;
|
||||
}
|
||||
maxWidth += component.getMeasuredWidth();
|
||||
}
|
||||
}
|
||||
|
||||
Rect paddings = getPaddings();
|
||||
maxWidth = maxWidth + paddings.left + paddings.right;
|
||||
maxHeight = maxHeight + paddings.top + paddings.bottom;
|
||||
|
||||
setMeasuredSize(getDefaultSize(maxWidth, widthSpec), getDefaultSize(maxHeight, heightSpec));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changeSize, int left, int top, int right, int bottom) {
|
||||
int width = getWidth();
|
||||
int height = getHeight();
|
||||
Rect paddings = getPaddings();
|
||||
int componentLeft;
|
||||
int componentTop;
|
||||
|
||||
if (mOrientation == VERTICAL) {
|
||||
componentTop = paddings.top;
|
||||
|
||||
for (int i = 0, n = getComponentCount(); i < n; i++) {
|
||||
GLView component = getComponent(i);
|
||||
if (component.getVisibility() == GONE) {
|
||||
continue;
|
||||
}
|
||||
int measureWidth = component.getMeasuredWidth();
|
||||
int measureHeight = component.getMeasuredHeight();
|
||||
|
||||
LayoutParams lp = (LayoutParams) component.getLayoutParams();
|
||||
componentLeft = getDefaultBegin(width, measureWidth, paddings.left, paddings.right,
|
||||
Gravity.getPosition(lp.gravity, Gravity.HORIZONTAL));
|
||||
component.layout(componentLeft, componentTop,
|
||||
componentLeft + measureWidth, componentTop + measureHeight);
|
||||
|
||||
componentTop += measureHeight + mInterval;
|
||||
}
|
||||
} else if (mOrientation == HORIZONTAL) {
|
||||
componentLeft = paddings.left;
|
||||
|
||||
for (int i = 0, n = getComponentCount(); i < n; i++) {
|
||||
GLView component = getComponent(i);
|
||||
if (component.getVisibility() == GONE) {
|
||||
continue;
|
||||
}
|
||||
int measureWidth = component.getMeasuredWidth();
|
||||
int measureHeight = component.getMeasuredHeight();
|
||||
|
||||
LayoutParams lp = (LayoutParams) component.getLayoutParams();
|
||||
componentTop = getDefaultBegin(height, measureHeight, paddings.top, paddings.bottom,
|
||||
Gravity.getPosition(lp.gravity, Gravity.VERTICAL));
|
||||
component.layout(componentLeft, componentTop,
|
||||
componentLeft + measureWidth, componentTop + measureHeight);
|
||||
|
||||
componentLeft += measureWidth + mInterval;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean checkLayoutParams(GLView.LayoutParams p) {
|
||||
return p instanceof LayoutParams;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GLView.LayoutParams generateDefaultLayoutParams() {
|
||||
return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GLView.LayoutParams generateLayoutParams(GLView.LayoutParams p) {
|
||||
return p == null ? generateDefaultLayoutParams() : new LayoutParams(p);
|
||||
}
|
||||
|
||||
@IntDef({HORIZONTAL, VERTICAL})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface OrientationMode {
|
||||
}
|
||||
|
||||
public static class LayoutParams extends GravityLayoutParams {
|
||||
|
||||
public float weight = 0.0f;
|
||||
|
||||
public LayoutParams(GLView.LayoutParams source) {
|
||||
super(source);
|
||||
|
||||
if (source instanceof LayoutParams) {
|
||||
weight = ((LayoutParams) source).weight;
|
||||
}
|
||||
}
|
||||
|
||||
public LayoutParams(int width, int height) {
|
||||
super(width, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.widget;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.hippo.glview.glrenderer.GLCanvas;
|
||||
import com.hippo.glview.glrenderer.MovableTextTexture;
|
||||
import com.hippo.glview.view.GLView;
|
||||
import com.hippo.glview.view.Gravity;
|
||||
import com.hippo.yorozuya.collect.CollectionUtils;
|
||||
|
||||
public class GLMovableTextView extends GLView {
|
||||
|
||||
MovableTextTexture mTextTexture;
|
||||
|
||||
private String mText = "";
|
||||
private int[] mIndexes = CollectionUtils.EMPTY_INT_ARRAY;
|
||||
|
||||
private int mGravity = Gravity.NO_GRAVITY;
|
||||
|
||||
private void generateIndexes() {
|
||||
if (mTextTexture == null || TextUtils.isEmpty(mText)) {
|
||||
mIndexes = CollectionUtils.EMPTY_INT_ARRAY;
|
||||
} else {
|
||||
mIndexes = mTextTexture.getTextIndexes(mText);
|
||||
}
|
||||
}
|
||||
|
||||
public void setTextTexture(MovableTextTexture textTexture) {
|
||||
if (mTextTexture == textTexture) {
|
||||
return;
|
||||
}
|
||||
mTextTexture = textTexture;
|
||||
|
||||
generateIndexes();
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
public void setText(String text) {
|
||||
if (text == null) {
|
||||
text = "";
|
||||
}
|
||||
if (text.equals(mText)) {
|
||||
return;
|
||||
}
|
||||
mText = text;
|
||||
|
||||
generateIndexes();
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
public void setGravity(int gravity) {
|
||||
if (mGravity != gravity) {
|
||||
mGravity = gravity;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getSuggestedMinimumWidth() {
|
||||
if (mTextTexture == null) {
|
||||
return super.getSuggestedMinimumWidth();
|
||||
} else {
|
||||
return Math.max((int) mTextTexture.getTextWidth(mIndexes) + mPaddings.left + mPaddings.right,
|
||||
super.getSuggestedMinimumWidth());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getSuggestedMinimumHeight() {
|
||||
if (mTextTexture == null) {
|
||||
return super.getSuggestedMinimumHeight();
|
||||
} else {
|
||||
return Math.max((int) mTextTexture.getTextHeight() + mPaddings.top + mPaddings.bottom,
|
||||
super.getSuggestedMinimumHeight());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRender(GLCanvas canvas) {
|
||||
if (mTextTexture == null || mIndexes == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Rect paddings = getPaddings();
|
||||
int x = getDefaultBegin(getWidth(), (int) mTextTexture.getTextWidth(mIndexes),
|
||||
paddings.left, paddings.right, Gravity.getPosition(mGravity, Gravity.HORIZONTAL));
|
||||
int y = getDefaultBegin(getHeight(), (int) mTextTexture.getTextHeight(),
|
||||
paddings.top, paddings.bottom, Gravity.getPosition(mGravity, Gravity.VERTICAL));
|
||||
mTextTexture.drawText(canvas, mIndexes, x, y);
|
||||
}
|
||||
}
|
||||
@@ -1,239 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.widget;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Path;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.view.animation.LinearInterpolator;
|
||||
|
||||
import androidx.core.view.animation.PathInterpolatorCompat;
|
||||
|
||||
import com.hippo.glview.anim.Animation;
|
||||
import com.hippo.glview.anim.FloatAnimation;
|
||||
import com.hippo.glview.glrenderer.GLCanvas;
|
||||
import com.hippo.glview.glrenderer.GLPaint;
|
||||
import com.hippo.glview.view.AnimationTime;
|
||||
import com.hippo.glview.view.GLView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class GLProgressView extends GLView {
|
||||
|
||||
private static final Interpolator TRIM_START_INTERPOLATOR;
|
||||
private static final Interpolator TRIM_END_INTERPOLATOR;
|
||||
private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
|
||||
|
||||
static {
|
||||
Path trimStartPath = new Path();
|
||||
trimStartPath.moveTo(0.0f, 0.0f);
|
||||
trimStartPath.lineTo(0.5f, 0.0f);
|
||||
trimStartPath.cubicTo(0.7f, 0.0f, 0.6f, 1f, 1f, 1f);
|
||||
TRIM_START_INTERPOLATOR = PathInterpolatorCompat.create(trimStartPath);
|
||||
|
||||
Path trimEndPath = new Path();
|
||||
trimEndPath.moveTo(0.0f, 0.0f);
|
||||
trimEndPath.cubicTo(0.2f, 0.0f, 0.1f, 1f, 0.5f, 1f);
|
||||
trimEndPath.lineTo(1f, 1f);
|
||||
TRIM_END_INTERPOLATOR = PathInterpolatorCompat.create(trimEndPath);
|
||||
}
|
||||
|
||||
private final GLPaint mGLPaint;
|
||||
private final List<Animation> mAnimations;
|
||||
private float mCx;
|
||||
private float mCy;
|
||||
private float mRadiusX;
|
||||
private float mRadiusY;
|
||||
private float mTrimStart = 0.0f;
|
||||
private float mTrimEnd = 0.0f;
|
||||
private float mTrimOffset = 0.0f;
|
||||
private float mTrimRotation = 0.0f;
|
||||
private boolean mIndeterminate = false;
|
||||
|
||||
public GLProgressView() {
|
||||
mGLPaint = new GLPaint();
|
||||
mGLPaint.setColor(Color.WHITE);
|
||||
mGLPaint.setBackgroundColor(Color.BLACK);
|
||||
mAnimations = new ArrayList<>();
|
||||
|
||||
setupAnimations();
|
||||
}
|
||||
|
||||
public void setupAnimations() {
|
||||
FloatAnimation trimStart = new FloatAnimation() {
|
||||
@Override
|
||||
protected void onCalculate(float progress) {
|
||||
super.onCalculate(progress);
|
||||
mTrimStart = get();
|
||||
}
|
||||
};
|
||||
trimStart.setRange(0.0f, 0.75f);
|
||||
trimStart.setDuration(1333L);
|
||||
trimStart.setInterpolator(TRIM_START_INTERPOLATOR);
|
||||
trimStart.setRepeatCount(Animation.INFINITE);
|
||||
|
||||
FloatAnimation trimEnd = new FloatAnimation() {
|
||||
@Override
|
||||
protected void onCalculate(float progress) {
|
||||
super.onCalculate(progress);
|
||||
mTrimEnd = get();
|
||||
}
|
||||
};
|
||||
trimEnd.setRange(0.0f, 0.75f);
|
||||
trimEnd.setDuration(1333L);
|
||||
trimEnd.setInterpolator(TRIM_END_INTERPOLATOR);
|
||||
trimEnd.setRepeatCount(Animation.INFINITE);
|
||||
|
||||
FloatAnimation trimOffset = new FloatAnimation() {
|
||||
@Override
|
||||
protected void onCalculate(float progress) {
|
||||
super.onCalculate(progress);
|
||||
mTrimOffset = get();
|
||||
}
|
||||
};
|
||||
trimOffset.setRange(0.0f, 0.25f);
|
||||
trimOffset.setDuration(1333L);
|
||||
trimOffset.setInterpolator(LINEAR_INTERPOLATOR);
|
||||
trimOffset.setRepeatCount(Animation.INFINITE);
|
||||
|
||||
FloatAnimation trimRotation = new FloatAnimation() {
|
||||
@Override
|
||||
protected void onCalculate(float progress) {
|
||||
super.onCalculate(progress);
|
||||
mTrimRotation = get();
|
||||
}
|
||||
};
|
||||
trimRotation.setRange(0.0f, 720.0f);
|
||||
trimRotation.setDuration(6665L);
|
||||
trimRotation.setInterpolator(LINEAR_INTERPOLATOR);
|
||||
trimRotation.setRepeatCount(Animation.INFINITE);
|
||||
|
||||
mAnimations.add(trimStart);
|
||||
mAnimations.add(trimEnd);
|
||||
mAnimations.add(trimOffset);
|
||||
mAnimations.add(trimRotation);
|
||||
}
|
||||
|
||||
private void startAnimations() {
|
||||
List<Animation> animations = mAnimations;
|
||||
for (int i = 0, n = animations.size(); i < n; i++) {
|
||||
animations.get(i).reset();
|
||||
}
|
||||
}
|
||||
|
||||
private void stopAnimations() {
|
||||
List<Animation> animations = mAnimations;
|
||||
for (int i = 0, n = animations.size(); i < n; i++) {
|
||||
animations.get(i).cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changeSize, int left, int top, int right,
|
||||
int bottom) {
|
||||
super.onLayout(changeSize, left, top, right, bottom);
|
||||
|
||||
int width = right - left;
|
||||
int height = bottom - top;
|
||||
mGLPaint.setLineWidth(Math.min(width, height) / 12.0f);
|
||||
mCx = width / 2;
|
||||
mCy = height / 2;
|
||||
mRadiusX = width / 48.0f * 19.0f;
|
||||
mRadiusY = height / 48.0f * 19.0f;
|
||||
}
|
||||
|
||||
public void setColor(int color) {
|
||||
mGLPaint.setColor(color);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void setBgColor(int color) {
|
||||
mGLPaint.setBackgroundColor(color);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public boolean isIndeterminate() {
|
||||
return mIndeterminate;
|
||||
}
|
||||
|
||||
public void setIndeterminate(boolean indeterminate) {
|
||||
if (mIndeterminate != indeterminate) {
|
||||
mIndeterminate = indeterminate;
|
||||
if (indeterminate) {
|
||||
startAnimations();
|
||||
} else {
|
||||
stopAnimations();
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public void setProgress(float progress) {
|
||||
if (!mIndeterminate) {
|
||||
mTrimStart = 0.0f;
|
||||
mTrimEnd = progress;
|
||||
mTrimOffset = 0.0f;
|
||||
mTrimRotation = 0.0f;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRender(GLCanvas canvas) {
|
||||
update();
|
||||
|
||||
int width = getWidth();
|
||||
int height = getHeight();
|
||||
int cx = width / 2;
|
||||
int cy = height / 2;
|
||||
|
||||
float startAngle = (mTrimStart + mTrimOffset) * 360.0f - 90;
|
||||
float sweepAngle = Math.max(12.0f, (mTrimEnd - mTrimStart) * 360.0f);
|
||||
float rotation = mTrimRotation + startAngle;
|
||||
|
||||
canvas.save();
|
||||
|
||||
canvas.translate(cx, cy);
|
||||
canvas.rotate(rotation, 0, 0, 1);
|
||||
if (rotation % 180 != 0) {
|
||||
canvas.translate(-cy, -cx);
|
||||
} else {
|
||||
canvas.translate(-cx, -cy);
|
||||
}
|
||||
|
||||
canvas.drawArc(mCx, mCy, mRadiusX, mRadiusY, sweepAngle, mGLPaint);
|
||||
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
private void update() {
|
||||
boolean invalidate = false;
|
||||
|
||||
if (mIndeterminate) {
|
||||
long currentTime = AnimationTime.get();
|
||||
List<Animation> animations = mAnimations;
|
||||
for (int i = 0, n = animations.size(); i < n; i++) {
|
||||
invalidate |= animations.get(i).calculate(currentTime);
|
||||
}
|
||||
}
|
||||
|
||||
if (invalidate) {
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.glview.widget;
|
||||
|
||||
import android.graphics.RectF;
|
||||
|
||||
import com.hippo.glview.glrenderer.GLCanvas;
|
||||
import com.hippo.glview.glrenderer.Texture;
|
||||
import com.hippo.glview.view.GLView;
|
||||
|
||||
public class GLTextureView extends GLView {
|
||||
|
||||
private final RectF mSrc = new RectF();
|
||||
private final RectF mDst = new RectF();
|
||||
|
||||
private Texture mTexture;
|
||||
|
||||
public Texture getTexture() {
|
||||
return mTexture;
|
||||
}
|
||||
|
||||
public void setTexture(Texture texture) {
|
||||
mTexture = texture;
|
||||
if (texture != null) {
|
||||
mSrc.set(0.0f, 0.0f, texture.getWidth(), texture.getHeight());
|
||||
} else {
|
||||
mSrc.setEmpty();
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getSuggestedMinimumWidth() {
|
||||
return mTexture == null ? super.getSuggestedMinimumWidth() : mTexture.getWidth() + mPaddings.width();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getSuggestedMinimumHeight() {
|
||||
return mTexture == null ? super.getSuggestedMinimumWidth() : mTexture.getHeight() + mPaddings.height();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRender(GLCanvas canvas) {
|
||||
if (mTexture == null || mSrc.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
mDst.set(mPaddings.left, mPaddings.top, getWidth() - mPaddings.right, getHeight() - mPaddings.bottom);
|
||||
if (mDst.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
mTexture.draw(canvas, mSrc, mDst);
|
||||
}
|
||||
}
|
||||
@@ -17,19 +17,14 @@
|
||||
*/
|
||||
package com.hippo.image
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.ImageDecoder
|
||||
import android.graphics.ImageDecoder.ALLOCATOR_SOFTWARE
|
||||
import android.graphics.ImageDecoder.ALLOCATOR_HARDWARE
|
||||
import android.graphics.ImageDecoder.DecodeException
|
||||
import android.graphics.ImageDecoder.ImageInfo
|
||||
import android.graphics.ImageDecoder.Source
|
||||
import android.graphics.PixelFormat
|
||||
import android.graphics.drawable.AnimatedImageDrawable
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import androidx.core.graphics.drawable.toDrawable
|
||||
import com.hippo.ehviewer.EhApplication
|
||||
import java.io.FileInputStream
|
||||
import java.nio.ByteBuffer
|
||||
@@ -37,23 +32,15 @@ import java.nio.channels.FileChannel
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
class Image private constructor(
|
||||
source: Source?, drawable: Drawable? = null,
|
||||
val release: () -> Unit? = {}
|
||||
) {
|
||||
private var mObtainedDrawable: Drawable?
|
||||
private var mBitmap: Bitmap? = null
|
||||
private var mCanvas: Canvas? = null
|
||||
class Image private constructor(source: Source?, val release: () -> Unit = {}) {
|
||||
var mObtainedDrawable: Drawable?
|
||||
|
||||
init {
|
||||
mObtainedDrawable = null
|
||||
source?.let {
|
||||
mObtainedDrawable =
|
||||
ImageDecoder.decodeDrawable(source) { decoder: ImageDecoder, info: ImageInfo, src: Source ->
|
||||
decoder.allocator = ALLOCATOR_SOFTWARE
|
||||
// Sadly we must use software memory since we need copy it to tile buffer, fuck glgallery
|
||||
// Idk it will cause how much performance regression
|
||||
|
||||
decoder.allocator = ALLOCATOR_HARDWARE
|
||||
decoder.setTargetSampleSize(
|
||||
max(
|
||||
min(
|
||||
@@ -64,16 +51,10 @@ class Image private constructor(
|
||||
)
|
||||
} // Should we lazy decode it?
|
||||
}
|
||||
if (mObtainedDrawable == null) {
|
||||
mObtainedDrawable = drawable!!
|
||||
}
|
||||
}
|
||||
|
||||
val animated = mObtainedDrawable is AnimatedImageDrawable
|
||||
val width = mObtainedDrawable!!.intrinsicWidth
|
||||
val height = mObtainedDrawable!!.intrinsicHeight
|
||||
val isRecycled = mObtainedDrawable == null
|
||||
|
||||
var started = false
|
||||
|
||||
@Synchronized
|
||||
@@ -86,59 +67,15 @@ class Image private constructor(
|
||||
}
|
||||
mObtainedDrawable?.callback = null
|
||||
mObtainedDrawable = null
|
||||
mCanvas = null
|
||||
mBitmap?.recycle()
|
||||
mBitmap = null
|
||||
release()
|
||||
}
|
||||
|
||||
private fun prepareBitmap() {
|
||||
if (mBitmap != null) return
|
||||
mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||
mCanvas = Canvas(mBitmap!!)
|
||||
}
|
||||
|
||||
private fun updateBitmap() {
|
||||
prepareBitmap()
|
||||
mObtainedDrawable!!.draw(mCanvas!!)
|
||||
}
|
||||
|
||||
fun texImage(init: Boolean, offsetX: Int, offsetY: Int, width: Int, height: Int) {
|
||||
val bitmap: Bitmap? = if (animated) {
|
||||
updateBitmap()
|
||||
mBitmap
|
||||
} else {
|
||||
(mObtainedDrawable as BitmapDrawable?)?.bitmap
|
||||
}
|
||||
bitmap ?: return
|
||||
nativeTexImage(
|
||||
bitmap,
|
||||
init,
|
||||
offsetX,
|
||||
offsetY,
|
||||
width,
|
||||
height
|
||||
)
|
||||
}
|
||||
|
||||
fun start() {
|
||||
if (!started) {
|
||||
(mObtainedDrawable as AnimatedImageDrawable?)?.start()
|
||||
}
|
||||
}
|
||||
|
||||
val delay: Int
|
||||
get() {
|
||||
if (animated)
|
||||
return 10
|
||||
return 0
|
||||
}
|
||||
|
||||
val isOpaque: Boolean
|
||||
get() {
|
||||
return mObtainedDrawable?.opacity == PixelFormat.OPAQUE
|
||||
}
|
||||
|
||||
companion object {
|
||||
var screenWidth: Int = 0
|
||||
var screenHeight: Int = 0
|
||||
@@ -169,20 +106,5 @@ class Image private constructor(
|
||||
release()
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun create(bitmap: Bitmap): Image {
|
||||
return Image(null, bitmap.toDrawable(Resources.getSystem()))
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
private external fun nativeTexImage(
|
||||
bitmap: Bitmap,
|
||||
init: Boolean,
|
||||
offsetX: Int,
|
||||
offsetY: Int,
|
||||
width: Int,
|
||||
height: Int
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,181 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.widget;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.os.BatteryManager;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import androidx.appcompat.widget.AppCompatTextView;
|
||||
|
||||
import com.hippo.drawable.BatteryDrawable;
|
||||
import com.hippo.ehviewer.R;
|
||||
|
||||
public class BatteryView extends AppCompatTextView {
|
||||
|
||||
private int mColor;
|
||||
private int mWarningColor;
|
||||
private int mCurrentColor;
|
||||
|
||||
private int mLevel = 0;
|
||||
private boolean mCharging = false;
|
||||
|
||||
private BatteryDrawable mDrawable;
|
||||
private boolean mAttached = false;
|
||||
private boolean mIsChargerWorking = false;
|
||||
|
||||
public BatteryView(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public BatteryView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public BatteryView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
|
||||
init();
|
||||
|
||||
TypedArray typedArray = context.obtainStyledAttributes(
|
||||
attrs, R.styleable.BatteryView, defStyleAttr, 0);
|
||||
mColor = typedArray.getColor(R.styleable.BatteryView_color, Color.WHITE);
|
||||
mWarningColor = typedArray.getColor(R.styleable.BatteryView_warningColor, Color.RED);
|
||||
typedArray.recycle();
|
||||
|
||||
mDrawable.setColor(mColor);
|
||||
mDrawable.setWarningColor(mWarningColor);
|
||||
}
|
||||
|
||||
private void init() {
|
||||
mDrawable = new BatteryDrawable();
|
||||
int height = (int) getTextSize();
|
||||
mDrawable.setBounds(0, 0, (int) (height / 0.618f), height);
|
||||
setCompoundDrawables(mDrawable, null, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTextColor(int color) {
|
||||
if (mCurrentColor == color) {
|
||||
return;
|
||||
}
|
||||
mCurrentColor = color;
|
||||
super.setTextColor(color);
|
||||
}
|
||||
|
||||
private void startCharger() {
|
||||
if (!mIsChargerWorking) {
|
||||
getHandler().post(mCharger);
|
||||
mIsChargerWorking = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void stopCharger() {
|
||||
if (mIsChargerWorking) {
|
||||
getHandler().removeCallbacks(mCharger);
|
||||
mIsChargerWorking = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
|
||||
if (!mAttached) {
|
||||
mAttached = true;
|
||||
|
||||
registerReceiver();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
|
||||
if (mAttached) {
|
||||
unregisterReceiver();
|
||||
stopCharger();
|
||||
mAttached = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void registerReceiver() {
|
||||
final IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(Intent.ACTION_BATTERY_CHANGED);
|
||||
getContext().registerReceiver(mIntentReceiver, filter, null, getHandler());
|
||||
} private final Runnable mCharger = new Runnable() {
|
||||
|
||||
private int level = 0;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
level += 2;
|
||||
if (level > 100) {
|
||||
level = 0;
|
||||
}
|
||||
mDrawable.setElect(level, false);
|
||||
getHandler().postDelayed(mCharger, 200);
|
||||
}
|
||||
};
|
||||
|
||||
private void unregisterReceiver() {
|
||||
getContext().unregisterReceiver(mIntentReceiver);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
|
||||
|
||||
@Override
|
||||
@SuppressLint("SetTextI18n")
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
|
||||
int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
|
||||
* 100 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
|
||||
boolean charging = (intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1)
|
||||
== BatteryManager.BATTERY_STATUS_CHARGING);
|
||||
|
||||
if (mLevel != level || mCharging != charging) {
|
||||
mLevel = level;
|
||||
mCharging = charging;
|
||||
if (mCharging && mLevel != 100) {
|
||||
startCharger();
|
||||
} else {
|
||||
stopCharger();
|
||||
mDrawable.setElect(mLevel);
|
||||
}
|
||||
|
||||
if (level <= BatteryDrawable.WARN_LIMIT && !charging) {
|
||||
setTextColor(mWarningColor);
|
||||
} else {
|
||||
setTextColor(mColor);
|
||||
}
|
||||
setText(mLevel + "%");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
public class ColorView extends View {
|
||||
|
||||
private int mColor;
|
||||
|
||||
public ColorView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public ColorView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public ColorView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public void setColor(int color) {
|
||||
if (mColor != color) {
|
||||
mColor = color;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
canvas.drawColor(mColor);
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.widget;
|
||||
|
||||
final class DateUtils {
|
||||
|
||||
public static final char QUOTE = '\'';
|
||||
public static final char SECONDS = 's';
|
||||
|
||||
public static boolean hasSeconds(CharSequence inFormat) {
|
||||
return hasDesignator(inFormat, SECONDS);
|
||||
}
|
||||
|
||||
public static boolean hasDesignator(CharSequence inFormat, char designator) {
|
||||
if (inFormat == null) return false;
|
||||
|
||||
final int length = inFormat.length();
|
||||
|
||||
int c;
|
||||
int count;
|
||||
|
||||
for (int i = 0; i < length; i += count) {
|
||||
count = 1;
|
||||
c = inFormat.charAt(i);
|
||||
|
||||
if (c == QUOTE) {
|
||||
count = skipQuotedText(inFormat, i, length);
|
||||
} else if (c == designator) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static int skipQuotedText(CharSequence s, int i, int len) {
|
||||
if (i + 1 < len && s.charAt(i + 1) == QUOTE) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
int count = 1;
|
||||
// skip leading quote
|
||||
i++;
|
||||
|
||||
while (i < len) {
|
||||
char c = s.charAt(i);
|
||||
|
||||
if (c == QUOTE) {
|
||||
count++;
|
||||
// QUOTEQUOTE -> QUOTE
|
||||
if (i + 1 < len && s.charAt(i + 1) == QUOTE) {
|
||||
i++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
i++;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
@@ -1,268 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.widget;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.database.ContentObserver;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.SystemClock;
|
||||
import android.provider.Settings;
|
||||
import android.text.format.DateFormat;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.ViewDebug.ExportedProperty;
|
||||
|
||||
import androidx.appcompat.widget.AppCompatTextView;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.TimeZone;
|
||||
|
||||
public class TextClock extends AppCompatTextView {
|
||||
|
||||
public static final CharSequence DEFAULT_FORMAT_12_HOUR = "hh:mm a";
|
||||
public static final CharSequence DEFAULT_FORMAT_24_HOUR;
|
||||
|
||||
static {
|
||||
DEFAULT_FORMAT_24_HOUR = "HH:mm";
|
||||
}
|
||||
|
||||
private CharSequence mFormat12;
|
||||
private CharSequence mFormat24;
|
||||
|
||||
@ExportedProperty
|
||||
private CharSequence mFormat;
|
||||
@ExportedProperty
|
||||
private boolean mHasSeconds;
|
||||
|
||||
private boolean mAttached;
|
||||
|
||||
private Calendar mTime;
|
||||
private String mTimeZone;
|
||||
private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (mTimeZone == null && Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) {
|
||||
final String timeZone = intent.getStringExtra("time-zone");
|
||||
createTime(timeZone);
|
||||
}
|
||||
onTimeChanged();
|
||||
}
|
||||
};
|
||||
|
||||
public TextClock(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public TextClock(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public TextClock(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
|
||||
mFormat12 = DEFAULT_FORMAT_12_HOUR;
|
||||
mFormat24 = DEFAULT_FORMAT_24_HOUR;
|
||||
mTimeZone = TimeZone.getDefault().getID();
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
createTime(mTimeZone);
|
||||
// Wait until onAttachedToWindow() to handle the ticker
|
||||
chooseFormat(false);
|
||||
}
|
||||
|
||||
private void createTime(String timeZone) {
|
||||
if (timeZone != null) {
|
||||
mTime = Calendar.getInstance(TimeZone.getTimeZone(timeZone));
|
||||
} else {
|
||||
mTime = Calendar.getInstance();
|
||||
}
|
||||
}
|
||||
|
||||
public CharSequence getFormat12Hour() {
|
||||
return mFormat12;
|
||||
}
|
||||
|
||||
public void setFormat12Hour(CharSequence format) {
|
||||
mFormat12 = format;
|
||||
|
||||
chooseFormat();
|
||||
onTimeChanged();
|
||||
}
|
||||
|
||||
public CharSequence getFormat24Hour() {
|
||||
return mFormat24;
|
||||
}
|
||||
|
||||
public void setFormat24Hour(CharSequence format) {
|
||||
mFormat24 = format;
|
||||
|
||||
chooseFormat();
|
||||
onTimeChanged();
|
||||
}
|
||||
|
||||
public boolean is24HourModeEnabled() {
|
||||
return DateFormat.is24HourFormat(getContext());
|
||||
} private final Runnable mTicker = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
onTimeChanged();
|
||||
|
||||
long now = SystemClock.uptimeMillis();
|
||||
long next = now + (1000 - now % 1000);
|
||||
|
||||
getHandler().postAtTime(mTicker, next);
|
||||
}
|
||||
};
|
||||
|
||||
public String getTimeZone() {
|
||||
return mTimeZone;
|
||||
}
|
||||
|
||||
public void setTimeZone(String timeZone) {
|
||||
mTimeZone = timeZone;
|
||||
|
||||
createTime(timeZone);
|
||||
onTimeChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects either one of {@link #getFormat12Hour()} or {@link #getFormat24Hour()}
|
||||
* depending on whether the user has selected 24-hour format.
|
||||
* <p>
|
||||
* Calling this method does not schedule or unschedule the time ticker.
|
||||
*/
|
||||
private void chooseFormat() {
|
||||
chooseFormat(true);
|
||||
}
|
||||
|
||||
public CharSequence getFormat() {
|
||||
return mFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects either one of {@link #getFormat12Hour()} or {@link #getFormat24Hour()}
|
||||
* depending on whether the user has selected 24-hour format.
|
||||
*
|
||||
* @param handleTicker true if calling this method should schedule/unschedule the
|
||||
* time ticker, false otherwise
|
||||
*/
|
||||
private void chooseFormat(boolean handleTicker) {
|
||||
final boolean format24Requested = is24HourModeEnabled();
|
||||
|
||||
if (format24Requested) {
|
||||
mFormat = mFormat24;
|
||||
} else {
|
||||
mFormat = mFormat12;
|
||||
}
|
||||
|
||||
boolean hadSeconds = mHasSeconds;
|
||||
mHasSeconds = DateUtils.hasSeconds(mFormat);
|
||||
|
||||
if (handleTicker && mAttached && hadSeconds != mHasSeconds) {
|
||||
if (hadSeconds) getHandler().removeCallbacks(mTicker);
|
||||
else mTicker.run();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
|
||||
if (!mAttached) {
|
||||
mAttached = true;
|
||||
|
||||
registerReceiver();
|
||||
registerObserver();
|
||||
|
||||
createTime(mTimeZone);
|
||||
|
||||
if (mHasSeconds) {
|
||||
mTicker.run();
|
||||
} else {
|
||||
onTimeChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
|
||||
if (mAttached) {
|
||||
unregisterReceiver();
|
||||
unregisterObserver();
|
||||
|
||||
getHandler().removeCallbacks(mTicker);
|
||||
|
||||
mAttached = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void registerReceiver() {
|
||||
final IntentFilter filter = new IntentFilter();
|
||||
|
||||
filter.addAction(Intent.ACTION_TIME_TICK);
|
||||
filter.addAction(Intent.ACTION_TIME_CHANGED);
|
||||
filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
|
||||
|
||||
getContext().registerReceiver(mIntentReceiver, filter, null, getHandler());
|
||||
}
|
||||
|
||||
private void registerObserver() {
|
||||
final ContentResolver resolver = getContext().getContentResolver();
|
||||
resolver.registerContentObserver(Settings.System.CONTENT_URI, true, mFormatChangeObserver);
|
||||
}
|
||||
|
||||
private void unregisterReceiver() {
|
||||
getContext().unregisterReceiver(mIntentReceiver);
|
||||
}
|
||||
|
||||
private void unregisterObserver() {
|
||||
final ContentResolver resolver = getContext().getContentResolver();
|
||||
resolver.unregisterContentObserver(mFormatChangeObserver);
|
||||
}
|
||||
|
||||
private void onTimeChanged() {
|
||||
mTime.setTimeInMillis(System.currentTimeMillis());
|
||||
setText(DateFormat.format(mFormat, mTime));
|
||||
} private final ContentObserver mFormatChangeObserver = new ContentObserver(new Handler()) {
|
||||
@Override
|
||||
public void onChange(boolean selfChange) {
|
||||
chooseFormat();
|
||||
onTimeChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange(boolean selfChange, Uri uri) {
|
||||
chooseFormat();
|
||||
onTimeChanged();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.yorozuya;
|
||||
|
||||
public final class ColorUtils {
|
||||
private ColorUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check is it the color opaque
|
||||
*
|
||||
* @param color the color
|
||||
* @return true if it is opaque
|
||||
*/
|
||||
public static boolean isOpaque(int color) {
|
||||
return color >>> 24 == 0xFF;
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.yorozuya;
|
||||
|
||||
public class Pool<T> {
|
||||
|
||||
private final T[] mArray;
|
||||
private final int mMaxSize;
|
||||
private int mSize;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Pool(int size) {
|
||||
if (size <= 0) {
|
||||
throw new IllegalStateException("Pool size must > 0, it is " + size);
|
||||
}
|
||||
mArray = (T[]) new Object[size];
|
||||
mMaxSize = size;
|
||||
mSize = 0;
|
||||
}
|
||||
|
||||
public void push(T t) {
|
||||
if (t != null && mSize < mMaxSize) {
|
||||
mArray[mSize++] = t;
|
||||
}
|
||||
}
|
||||
|
||||
public T pop() {
|
||||
if (mSize > 0) {
|
||||
T t = mArray[--mSize];
|
||||
mArray[mSize] = null;
|
||||
return t;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loaded 100 of 219 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user