pull/37/head
parent
5d1fab40c8
commit
2b3a9f8ff4
@ -1,195 +0,0 @@ |
|||||||
package io.legado.app.ui.widget.page.curl; |
|
||||||
|
|
||||||
import android.graphics.Bitmap; |
|
||||||
import android.graphics.Canvas; |
|
||||||
import android.graphics.Color; |
|
||||||
import android.graphics.RectF; |
|
||||||
|
|
||||||
/** |
|
||||||
* Storage class for page textures, blend colors and possibly some other values |
|
||||||
* in the future. |
|
||||||
* |
|
||||||
* @author harism |
|
||||||
*/ |
|
||||||
public class CurlPage { |
|
||||||
|
|
||||||
public static final int SIDE_BACK = 2; |
|
||||||
public static final int SIDE_BOTH = 3; |
|
||||||
public static final int SIDE_FRONT = 1; |
|
||||||
|
|
||||||
private int mColorBack; |
|
||||||
private int mColorFront; |
|
||||||
private Bitmap mTextureBack; |
|
||||||
private Bitmap mTextureFront; |
|
||||||
private boolean mTexturesChanged; |
|
||||||
|
|
||||||
/** |
|
||||||
* Default constructor. |
|
||||||
*/ |
|
||||||
public CurlPage() { |
|
||||||
reset(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Getter for color. |
|
||||||
*/ |
|
||||||
public int getColor(int side) { |
|
||||||
switch (side) { |
|
||||||
case SIDE_FRONT: |
|
||||||
return mColorFront; |
|
||||||
default: |
|
||||||
return mColorBack; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Calculates the next highest power of two for a given integer. |
|
||||||
*/ |
|
||||||
private int getNextHighestPO2(int n) { |
|
||||||
n -= 1; |
|
||||||
n = n | (n >> 1); |
|
||||||
n = n | (n >> 2); |
|
||||||
n = n | (n >> 4); |
|
||||||
n = n | (n >> 8); |
|
||||||
n = n | (n >> 16); |
|
||||||
n = n | (n >> 32); |
|
||||||
return n + 1; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Generates nearest power of two sized Bitmap for give Bitmap. Returns this |
|
||||||
* new Bitmap using default return statement + original texture coordinates |
|
||||||
* are stored into RectF. |
|
||||||
*/ |
|
||||||
private Bitmap getTexture(Bitmap bitmap, RectF textureRect) { |
|
||||||
// Bitmap original size.
|
|
||||||
int w = bitmap.getWidth(); |
|
||||||
int h = bitmap.getHeight(); |
|
||||||
// Bitmap size expanded to next power of two. This is done due to
|
|
||||||
// the requirement on many devices, texture width and height should
|
|
||||||
// be power of two.
|
|
||||||
int newW = getNextHighestPO2(w); |
|
||||||
int newH = getNextHighestPO2(h); |
|
||||||
|
|
||||||
// TODO: Is there another way to create a bigger Bitmap and copy
|
|
||||||
// original Bitmap to it more efficiently? Immutable bitmap anyone?
|
|
||||||
Bitmap bitmapTex = Bitmap.createBitmap(newW, newH, bitmap.getConfig()); |
|
||||||
Canvas c = new Canvas(bitmapTex); |
|
||||||
c.drawBitmap(bitmap, 0, 0, null); |
|
||||||
|
|
||||||
// Calculate final texture coordinates.
|
|
||||||
float texX = (float) w / newW; |
|
||||||
float texY = (float) h / newH; |
|
||||||
textureRect.set(0f, 0f, texX, texY); |
|
||||||
|
|
||||||
return bitmapTex; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Getter for textures. Creates Bitmap sized to nearest power of two, copies |
|
||||||
* original Bitmap into it and returns it. RectF given as parameter is |
|
||||||
* filled with actual texture coordinates in this new upscaled texture |
|
||||||
* Bitmap. |
|
||||||
*/ |
|
||||||
public Bitmap getTexture(RectF textureRect, int side) { |
|
||||||
switch (side) { |
|
||||||
case SIDE_FRONT: |
|
||||||
return getTexture(mTextureFront, textureRect); |
|
||||||
default: |
|
||||||
return getTexture(mTextureBack, textureRect); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Returns true if textures have changed. |
|
||||||
*/ |
|
||||||
public boolean getTexturesChanged() { |
|
||||||
return mTexturesChanged; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Returns true if back siding texture exists and it differs from front |
|
||||||
* facing one. |
|
||||||
*/ |
|
||||||
public boolean hasBackTexture() { |
|
||||||
return !mTextureFront.equals(mTextureBack); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Recycles and frees underlying Bitmaps. |
|
||||||
*/ |
|
||||||
public void recycle() { |
|
||||||
if (mTextureFront != null) { |
|
||||||
mTextureFront.recycle(); |
|
||||||
} |
|
||||||
mTextureFront = Bitmap.createBitmap(1, 1, Bitmap.Config.RGB_565); |
|
||||||
mTextureFront.eraseColor(mColorFront); |
|
||||||
if (mTextureBack != null) { |
|
||||||
mTextureBack.recycle(); |
|
||||||
} |
|
||||||
mTextureBack = Bitmap.createBitmap(1, 1, Bitmap.Config.RGB_565); |
|
||||||
mTextureBack.eraseColor(mColorBack); |
|
||||||
mTexturesChanged = false; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Resets this CurlPage into its initial state. |
|
||||||
*/ |
|
||||||
public void reset() { |
|
||||||
mColorBack = Color.WHITE; |
|
||||||
mColorFront = Color.WHITE; |
|
||||||
recycle(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Setter blend color. |
|
||||||
*/ |
|
||||||
public void setColor(int color, int side) { |
|
||||||
switch (side) { |
|
||||||
case SIDE_FRONT: |
|
||||||
mColorFront = color; |
|
||||||
break; |
|
||||||
case SIDE_BACK: |
|
||||||
mColorBack = color; |
|
||||||
break; |
|
||||||
default: |
|
||||||
mColorFront = mColorBack = color; |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Setter for textures. |
|
||||||
*/ |
|
||||||
public void setTexture(Bitmap texture, int side) { |
|
||||||
if (texture == null) { |
|
||||||
texture = Bitmap.createBitmap(1, 1, Bitmap.Config.RGB_565); |
|
||||||
if (side == SIDE_BACK) { |
|
||||||
texture.eraseColor(mColorBack); |
|
||||||
} else { |
|
||||||
texture.eraseColor(mColorFront); |
|
||||||
} |
|
||||||
} |
|
||||||
switch (side) { |
|
||||||
case SIDE_FRONT: |
|
||||||
if (mTextureFront != null) |
|
||||||
mTextureFront.recycle(); |
|
||||||
mTextureFront = texture; |
|
||||||
break; |
|
||||||
case SIDE_BACK: |
|
||||||
if (mTextureBack != null) |
|
||||||
mTextureBack.recycle(); |
|
||||||
mTextureBack = texture; |
|
||||||
break; |
|
||||||
case SIDE_BOTH: |
|
||||||
if (mTextureFront != null) |
|
||||||
mTextureFront.recycle(); |
|
||||||
if (mTextureBack != null) |
|
||||||
mTextureBack.recycle(); |
|
||||||
mTextureFront = mTextureBack = texture; |
|
||||||
break; |
|
||||||
} |
|
||||||
mTexturesChanged = true; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -0,0 +1,191 @@ |
|||||||
|
package io.legado.app.ui.widget.page.curl |
||||||
|
|
||||||
|
import android.graphics.Bitmap |
||||||
|
import android.graphics.Canvas |
||||||
|
import android.graphics.Color |
||||||
|
import android.graphics.RectF |
||||||
|
|
||||||
|
/** |
||||||
|
* Storage class for page textures, blend colors and possibly some other values |
||||||
|
* in the future. |
||||||
|
* |
||||||
|
* @author harism |
||||||
|
*/ |
||||||
|
class CurlPage { |
||||||
|
|
||||||
|
private var mColorBack: Int = 0 |
||||||
|
private var mColorFront: Int = 0 |
||||||
|
private var mTextureBack: Bitmap? = null |
||||||
|
private var mTextureFront: Bitmap? = null |
||||||
|
/** |
||||||
|
* Returns true if textures have changed. |
||||||
|
*/ |
||||||
|
var texturesChanged: Boolean = false |
||||||
|
private set |
||||||
|
|
||||||
|
/** |
||||||
|
* Default constructor. |
||||||
|
*/ |
||||||
|
init { |
||||||
|
reset() |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Getter for color. |
||||||
|
*/ |
||||||
|
fun getColor(side: Int): Int { |
||||||
|
return when (side) { |
||||||
|
SIDE_FRONT -> mColorFront |
||||||
|
else -> mColorBack |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Calculates the next highest power of two for a given integer. |
||||||
|
*/ |
||||||
|
private fun getNextHighestPO2(n: Int): Int { |
||||||
|
var n1 = n |
||||||
|
n1 -= 1 |
||||||
|
n1 = n1 or (n1 shr 1) |
||||||
|
n1 = n1 or (n1 shr 2) |
||||||
|
n1 = n1 or (n1 shr 4) |
||||||
|
n1 = n1 or (n1 shr 8) |
||||||
|
n1 = n1 or (n1 shr 16) |
||||||
|
n1 = n1 or (n1 shr 32) |
||||||
|
return n1 + 1 |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Generates nearest power of two sized Bitmap for give Bitmap. Returns this |
||||||
|
* new Bitmap using default return statement + original texture coordinates |
||||||
|
* are stored into RectF. |
||||||
|
*/ |
||||||
|
private fun getTexture(bitmap: Bitmap, textureRect: RectF): Bitmap { |
||||||
|
// Bitmap original size. |
||||||
|
val w = bitmap.width |
||||||
|
val h = bitmap.height |
||||||
|
// Bitmap size expanded to next power of two. This is done due to |
||||||
|
// the requirement on many devices, texture width and height should |
||||||
|
// be power of two. |
||||||
|
val newW = getNextHighestPO2(w) |
||||||
|
val newH = getNextHighestPO2(h) |
||||||
|
|
||||||
|
// TODO: Is there another way to create a bigger Bitmap and copy |
||||||
|
// original Bitmap to it more efficiently? Immutable bitmap anyone? |
||||||
|
val bitmapTex = Bitmap.createBitmap(newW, newH, bitmap.config) |
||||||
|
val c = Canvas(bitmapTex) |
||||||
|
c.drawBitmap(bitmap, 0f, 0f, null) |
||||||
|
|
||||||
|
// Calculate final texture coordinates. |
||||||
|
val texX = w.toFloat() / newW |
||||||
|
val texY = h.toFloat() / newH |
||||||
|
textureRect.set(0f, 0f, texX, texY) |
||||||
|
|
||||||
|
return bitmapTex |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Getter for textures. Creates Bitmap sized to nearest power of two, copies |
||||||
|
* original Bitmap into it and returns it. RectF given as parameter is |
||||||
|
* filled with actual texture coordinates in this new upscaled texture |
||||||
|
* Bitmap. |
||||||
|
*/ |
||||||
|
fun getTexture(textureRect: RectF, side: Int): Bitmap { |
||||||
|
return when (side) { |
||||||
|
SIDE_FRONT -> getTexture(mTextureFront!!, textureRect) |
||||||
|
else -> getTexture(mTextureBack!!, textureRect) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns true if back siding texture exists and it differs from front |
||||||
|
* facing one. |
||||||
|
*/ |
||||||
|
fun hasBackTexture(): Boolean { |
||||||
|
return mTextureFront != mTextureBack |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Recycles and frees underlying Bitmaps. |
||||||
|
*/ |
||||||
|
fun recycle() { |
||||||
|
if (mTextureFront != null) { |
||||||
|
mTextureFront!!.recycle() |
||||||
|
} |
||||||
|
mTextureFront = Bitmap.createBitmap(1, 1, Bitmap.Config.RGB_565) |
||||||
|
mTextureFront!!.eraseColor(mColorFront) |
||||||
|
if (mTextureBack != null) { |
||||||
|
mTextureBack!!.recycle() |
||||||
|
} |
||||||
|
mTextureBack = Bitmap.createBitmap(1, 1, Bitmap.Config.RGB_565) |
||||||
|
mTextureBack!!.eraseColor(mColorBack) |
||||||
|
texturesChanged = false |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Resets this CurlPage into its initial state. |
||||||
|
*/ |
||||||
|
fun reset() { |
||||||
|
mColorBack = Color.WHITE |
||||||
|
mColorFront = Color.WHITE |
||||||
|
recycle() |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Setter blend color. |
||||||
|
*/ |
||||||
|
fun setColor(color: Int, side: Int) { |
||||||
|
when (side) { |
||||||
|
SIDE_FRONT -> mColorFront = color |
||||||
|
SIDE_BACK -> mColorBack = color |
||||||
|
else -> { |
||||||
|
mColorBack = color |
||||||
|
mColorFront = mColorBack |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Setter for textures. |
||||||
|
*/ |
||||||
|
fun setTexture(texture: Bitmap?, side: Int) { |
||||||
|
var texture1 = texture |
||||||
|
if (texture1 == null) { |
||||||
|
texture1 = Bitmap.createBitmap(1, 1, Bitmap.Config.RGB_565) |
||||||
|
if (side == SIDE_BACK) { |
||||||
|
texture1!!.eraseColor(mColorBack) |
||||||
|
} else { |
||||||
|
texture1!!.eraseColor(mColorFront) |
||||||
|
} |
||||||
|
} |
||||||
|
when (side) { |
||||||
|
SIDE_FRONT -> { |
||||||
|
if (mTextureFront != null) |
||||||
|
mTextureFront!!.recycle() |
||||||
|
mTextureFront = texture1 |
||||||
|
} |
||||||
|
SIDE_BACK -> { |
||||||
|
if (mTextureBack != null) |
||||||
|
mTextureBack!!.recycle() |
||||||
|
mTextureBack = texture1 |
||||||
|
} |
||||||
|
SIDE_BOTH -> { |
||||||
|
if (mTextureFront != null) |
||||||
|
mTextureFront!!.recycle() |
||||||
|
if (mTextureBack != null) |
||||||
|
mTextureBack!!.recycle() |
||||||
|
mTextureBack = texture1 |
||||||
|
mTextureFront = mTextureBack |
||||||
|
} |
||||||
|
} |
||||||
|
texturesChanged = true |
||||||
|
} |
||||||
|
|
||||||
|
companion object { |
||||||
|
|
||||||
|
const val SIDE_BACK = 2 |
||||||
|
const val SIDE_BOTH = 3 |
||||||
|
const val SIDE_FRONT = 1 |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -1,250 +0,0 @@ |
|||||||
package io.legado.app.ui.widget.page.curl; |
|
||||||
|
|
||||||
import android.graphics.Color; |
|
||||||
import android.graphics.PointF; |
|
||||||
import android.graphics.RectF; |
|
||||||
import android.opengl.GLSurfaceView; |
|
||||||
import android.opengl.GLU; |
|
||||||
|
|
||||||
import java.util.Vector; |
|
||||||
|
|
||||||
import javax.microedition.khronos.egl.EGLConfig; |
|
||||||
import javax.microedition.khronos.opengles.GL10; |
|
||||||
|
|
||||||
/** |
|
||||||
* Actual renderer class. |
|
||||||
* |
|
||||||
* @author harism |
|
||||||
*/ |
|
||||||
public class CurlRenderer implements GLSurfaceView.Renderer { |
|
||||||
|
|
||||||
// Constant for requesting left page rect.
|
|
||||||
public static final int PAGE_LEFT = 1; |
|
||||||
// Constant for requesting right page rect.
|
|
||||||
public static final int PAGE_RIGHT = 2; |
|
||||||
// Constants for changing view mode.
|
|
||||||
public static final int SHOW_ONE_PAGE = 1; |
|
||||||
public static final int SHOW_TWO_PAGES = 2; |
|
||||||
// Set to true for checking quickly how perspective projection looks.
|
|
||||||
private static final boolean USE_PERSPECTIVE_PROJECTION = false; |
|
||||||
// Background fill color.
|
|
||||||
private int mBackgroundColor; |
|
||||||
// Curl meshes used for static and dynamic rendering.
|
|
||||||
private Vector<CurlMesh> mCurlMeshes; |
|
||||||
private RectF mMargins = new RectF(); |
|
||||||
private CurlRenderer.Observer mObserver; |
|
||||||
// Page rectangles.
|
|
||||||
private RectF mPageRectLeft; |
|
||||||
private RectF mPageRectRight; |
|
||||||
// View mode.
|
|
||||||
private int mViewMode = SHOW_ONE_PAGE; |
|
||||||
// Screen size.
|
|
||||||
private int mViewportWidth, mViewportHeight; |
|
||||||
// Rect for render area.
|
|
||||||
private RectF mViewRect = new RectF(); |
|
||||||
|
|
||||||
/** |
|
||||||
* Basic constructor. |
|
||||||
*/ |
|
||||||
public CurlRenderer(CurlRenderer.Observer observer) { |
|
||||||
mObserver = observer; |
|
||||||
mCurlMeshes = new Vector<CurlMesh>(); |
|
||||||
mPageRectLeft = new RectF(); |
|
||||||
mPageRectRight = new RectF(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Adds CurlMesh to this renderer. |
|
||||||
*/ |
|
||||||
public synchronized void addCurlMesh(CurlMesh mesh) { |
|
||||||
removeCurlMesh(mesh); |
|
||||||
mCurlMeshes.add(mesh); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Returns rect reserved for left or right page. Value page should be |
|
||||||
* PAGE_LEFT or PAGE_RIGHT. |
|
||||||
*/ |
|
||||||
public RectF getPageRect(int page) { |
|
||||||
if (page == PAGE_LEFT) { |
|
||||||
return mPageRectLeft; |
|
||||||
} else if (page == PAGE_RIGHT) { |
|
||||||
return mPageRectRight; |
|
||||||
} |
|
||||||
return null; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public synchronized void onDrawFrame(GL10 gl) { |
|
||||||
|
|
||||||
mObserver.onDrawFrame(); |
|
||||||
|
|
||||||
gl.glClearColor(Color.red(mBackgroundColor) / 255f, |
|
||||||
Color.green(mBackgroundColor) / 255f, |
|
||||||
Color.blue(mBackgroundColor) / 255f, |
|
||||||
Color.alpha(mBackgroundColor) / 255f); |
|
||||||
gl.glClear(GL10.GL_COLOR_BUFFER_BIT); |
|
||||||
gl.glLoadIdentity(); |
|
||||||
|
|
||||||
if (USE_PERSPECTIVE_PROJECTION) { |
|
||||||
gl.glTranslatef(0, 0, -6f); |
|
||||||
} |
|
||||||
|
|
||||||
for (int i = 0; i < mCurlMeshes.size(); ++i) { |
|
||||||
mCurlMeshes.get(i).onDrawFrame(gl); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onSurfaceChanged(GL10 gl, int width, int height) { |
|
||||||
gl.glViewport(0, 0, width, height); |
|
||||||
mViewportWidth = width; |
|
||||||
mViewportHeight = height; |
|
||||||
|
|
||||||
float ratio = (float) width / height; |
|
||||||
mViewRect.top = 1.0f; |
|
||||||
mViewRect.bottom = -1.0f; |
|
||||||
mViewRect.left = -ratio; |
|
||||||
mViewRect.right = ratio; |
|
||||||
updatePageRects(); |
|
||||||
|
|
||||||
gl.glMatrixMode(GL10.GL_PROJECTION); |
|
||||||
gl.glLoadIdentity(); |
|
||||||
if (USE_PERSPECTIVE_PROJECTION) { |
|
||||||
GLU.gluPerspective(gl, 20f, (float) width / height, .1f, 100f); |
|
||||||
} else { |
|
||||||
GLU.gluOrtho2D(gl, mViewRect.left, mViewRect.right, |
|
||||||
mViewRect.bottom, mViewRect.top); |
|
||||||
} |
|
||||||
|
|
||||||
gl.glMatrixMode(GL10.GL_MODELVIEW); |
|
||||||
gl.glLoadIdentity(); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onSurfaceCreated(GL10 gl, EGLConfig config) { |
|
||||||
gl.glClearColor(0f, 0f, 0f, 1f); |
|
||||||
gl.glShadeModel(GL10.GL_SMOOTH); |
|
||||||
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST); |
|
||||||
gl.glHint(GL10.GL_LINE_SMOOTH_HINT, GL10.GL_NICEST); |
|
||||||
gl.glHint(GL10.GL_POLYGON_SMOOTH_HINT, GL10.GL_NICEST); |
|
||||||
gl.glEnable(GL10.GL_LINE_SMOOTH); |
|
||||||
gl.glDisable(GL10.GL_DEPTH_TEST); |
|
||||||
gl.glDisable(GL10.GL_CULL_FACE); |
|
||||||
|
|
||||||
mObserver.onSurfaceCreated(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Removes CurlMesh from this renderer. |
|
||||||
*/ |
|
||||||
public synchronized void removeCurlMesh(CurlMesh mesh) { |
|
||||||
while (mCurlMeshes.remove(mesh)) |
|
||||||
; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Change background/clear color. |
|
||||||
*/ |
|
||||||
public void setBackgroundColor(int color) { |
|
||||||
mBackgroundColor = color; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Set margins or padding. Note: margins are proportional. Meaning a value |
|
||||||
* of .1f will produce a 10% margin. |
|
||||||
*/ |
|
||||||
public synchronized void setMargins(float left, float top, float right, |
|
||||||
float bottom) { |
|
||||||
mMargins.left = left; |
|
||||||
mMargins.top = top; |
|
||||||
mMargins.right = right; |
|
||||||
mMargins.bottom = bottom; |
|
||||||
updatePageRects(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Sets visible page count to one or two. Should be either SHOW_ONE_PAGE or |
|
||||||
* SHOW_TWO_PAGES. |
|
||||||
*/ |
|
||||||
public synchronized void setViewMode(int viewmode) { |
|
||||||
if (viewmode == SHOW_ONE_PAGE) { |
|
||||||
mViewMode = viewmode; |
|
||||||
updatePageRects(); |
|
||||||
} else if (viewmode == SHOW_TWO_PAGES) { |
|
||||||
mViewMode = viewmode; |
|
||||||
updatePageRects(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Translates screen coordinates into view coordinates. |
|
||||||
*/ |
|
||||||
public void translate(PointF pt) { |
|
||||||
pt.x = mViewRect.left + (mViewRect.width() * pt.x / mViewportWidth); |
|
||||||
pt.y = mViewRect.top - (-mViewRect.height() * pt.y / mViewportHeight); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Recalculates page rectangles. |
|
||||||
*/ |
|
||||||
private void updatePageRects() { |
|
||||||
if (mViewRect.width() == 0 || mViewRect.height() == 0) { |
|
||||||
return; |
|
||||||
} else if (mViewMode == SHOW_ONE_PAGE) { |
|
||||||
mPageRectRight.set(mViewRect); |
|
||||||
mPageRectRight.left += mViewRect.width() * mMargins.left; |
|
||||||
mPageRectRight.right -= mViewRect.width() * mMargins.right; |
|
||||||
mPageRectRight.top += mViewRect.height() * mMargins.top; |
|
||||||
mPageRectRight.bottom -= mViewRect.height() * mMargins.bottom; |
|
||||||
|
|
||||||
mPageRectLeft.set(mPageRectRight); |
|
||||||
mPageRectLeft.offset(-mPageRectRight.width(), 0); |
|
||||||
|
|
||||||
int bitmapW = (int) ((mPageRectRight.width() * mViewportWidth) / mViewRect |
|
||||||
.width()); |
|
||||||
int bitmapH = (int) ((mPageRectRight.height() * mViewportHeight) / mViewRect |
|
||||||
.height()); |
|
||||||
mObserver.onPageSizeChanged(bitmapW, bitmapH); |
|
||||||
} else if (mViewMode == SHOW_TWO_PAGES) { |
|
||||||
mPageRectRight.set(mViewRect); |
|
||||||
mPageRectRight.left += mViewRect.width() * mMargins.left; |
|
||||||
mPageRectRight.right -= mViewRect.width() * mMargins.right; |
|
||||||
mPageRectRight.top += mViewRect.height() * mMargins.top; |
|
||||||
mPageRectRight.bottom -= mViewRect.height() * mMargins.bottom; |
|
||||||
|
|
||||||
mPageRectLeft.set(mPageRectRight); |
|
||||||
mPageRectLeft.right = (mPageRectLeft.right + mPageRectLeft.left) / 2; |
|
||||||
mPageRectRight.left = mPageRectLeft.right; |
|
||||||
|
|
||||||
int bitmapW = (int) ((mPageRectRight.width() * mViewportWidth) / mViewRect |
|
||||||
.width()); |
|
||||||
int bitmapH = (int) ((mPageRectRight.height() * mViewportHeight) / mViewRect |
|
||||||
.height()); |
|
||||||
mObserver.onPageSizeChanged(bitmapW, bitmapH); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Observer for waiting render engine/state updates. |
|
||||||
*/ |
|
||||||
public interface Observer { |
|
||||||
/** |
|
||||||
* Called from onDrawFrame called before rendering is started. This is |
|
||||||
* intended to be used for animation purposes. |
|
||||||
*/ |
|
||||||
public void onDrawFrame(); |
|
||||||
|
|
||||||
/** |
|
||||||
* Called once page size is changed. Width and height tell the page size |
|
||||||
* in pixels making it possible to update textures accordingly. |
|
||||||
*/ |
|
||||||
public void onPageSizeChanged(int width, int height); |
|
||||||
|
|
||||||
/** |
|
||||||
* Called from onSurfaceCreated to enable texture re-initialization etc |
|
||||||
* what needs to be done when this happens. |
|
||||||
*/ |
|
||||||
public void onSurfaceCreated(); |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,252 @@ |
|||||||
|
package io.legado.app.ui.widget.page.curl |
||||||
|
|
||||||
|
import android.graphics.Color |
||||||
|
import android.graphics.PointF |
||||||
|
import android.graphics.RectF |
||||||
|
import android.opengl.GLSurfaceView |
||||||
|
import android.opengl.GLU |
||||||
|
import java.util.* |
||||||
|
import javax.microedition.khronos.egl.EGLConfig |
||||||
|
import javax.microedition.khronos.opengles.GL10 |
||||||
|
|
||||||
|
/** |
||||||
|
* Actual renderer class. |
||||||
|
* |
||||||
|
* @author harism |
||||||
|
*/ |
||||||
|
class CurlRenderer |
||||||
|
/** |
||||||
|
* Basic constructor. |
||||||
|
*/ |
||||||
|
(private val mObserver: CurlRenderer.Observer) : GLSurfaceView.Renderer { |
||||||
|
// Background fill color. |
||||||
|
private var mBackgroundColor: Int = 0 |
||||||
|
// Curl meshes used for static and dynamic rendering. |
||||||
|
private val mCurlMeshes: Vector<CurlMesh> = Vector() |
||||||
|
private val mMargins = RectF() |
||||||
|
// Page rectangles. |
||||||
|
private val mPageRectLeft: RectF = RectF() |
||||||
|
private val mPageRectRight: RectF = RectF() |
||||||
|
// View mode. |
||||||
|
private var mViewMode = SHOW_ONE_PAGE |
||||||
|
// Screen size. |
||||||
|
private var mViewportWidth: Int = 0 |
||||||
|
private var mViewportHeight: Int = 0 |
||||||
|
// Rect for render area. |
||||||
|
private val mViewRect = RectF() |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds CurlMesh to this renderer. |
||||||
|
*/ |
||||||
|
@Synchronized |
||||||
|
fun addCurlMesh(mesh: CurlMesh) { |
||||||
|
removeCurlMesh(mesh) |
||||||
|
mCurlMeshes.add(mesh) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns rect reserved for left or right page. Value page should be |
||||||
|
* PAGE_LEFT or PAGE_RIGHT. |
||||||
|
*/ |
||||||
|
fun getPageRect(page: Int): RectF? { |
||||||
|
if (page == PAGE_LEFT) { |
||||||
|
return mPageRectLeft |
||||||
|
} else if (page == PAGE_RIGHT) { |
||||||
|
return mPageRectRight |
||||||
|
} |
||||||
|
return null |
||||||
|
} |
||||||
|
|
||||||
|
@Synchronized |
||||||
|
override fun onDrawFrame(gl: GL10) { |
||||||
|
|
||||||
|
mObserver.onDrawFrame() |
||||||
|
|
||||||
|
gl.glClearColor( |
||||||
|
Color.red(mBackgroundColor) / 255f, |
||||||
|
Color.green(mBackgroundColor) / 255f, |
||||||
|
Color.blue(mBackgroundColor) / 255f, |
||||||
|
Color.alpha(mBackgroundColor) / 255f |
||||||
|
) |
||||||
|
gl.glClear(GL10.GL_COLOR_BUFFER_BIT) |
||||||
|
gl.glLoadIdentity() |
||||||
|
|
||||||
|
if (USE_PERSPECTIVE_PROJECTION) { |
||||||
|
gl.glTranslatef(0f, 0f, -6f) |
||||||
|
} |
||||||
|
|
||||||
|
for (i in mCurlMeshes.indices) { |
||||||
|
mCurlMeshes[i].onDrawFrame(gl) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun onSurfaceChanged(gl: GL10, width: Int, height: Int) { |
||||||
|
gl.glViewport(0, 0, width, height) |
||||||
|
mViewportWidth = width |
||||||
|
mViewportHeight = height |
||||||
|
|
||||||
|
val ratio = width.toFloat() / height |
||||||
|
mViewRect.top = 1.0f |
||||||
|
mViewRect.bottom = -1.0f |
||||||
|
mViewRect.left = -ratio |
||||||
|
mViewRect.right = ratio |
||||||
|
updatePageRects() |
||||||
|
|
||||||
|
gl.glMatrixMode(GL10.GL_PROJECTION) |
||||||
|
gl.glLoadIdentity() |
||||||
|
if (USE_PERSPECTIVE_PROJECTION) { |
||||||
|
GLU.gluPerspective(gl, 20f, width.toFloat() / height, .1f, 100f) |
||||||
|
} else { |
||||||
|
GLU.gluOrtho2D( |
||||||
|
gl, mViewRect.left, mViewRect.right, |
||||||
|
mViewRect.bottom, mViewRect.top |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
gl.glMatrixMode(GL10.GL_MODELVIEW) |
||||||
|
gl.glLoadIdentity() |
||||||
|
} |
||||||
|
|
||||||
|
override fun onSurfaceCreated(gl: GL10, config: EGLConfig) { |
||||||
|
gl.glClearColor(0f, 0f, 0f, 1f) |
||||||
|
gl.glShadeModel(GL10.GL_SMOOTH) |
||||||
|
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST) |
||||||
|
gl.glHint(GL10.GL_LINE_SMOOTH_HINT, GL10.GL_NICEST) |
||||||
|
gl.glHint(GL10.GL_POLYGON_SMOOTH_HINT, GL10.GL_NICEST) |
||||||
|
gl.glEnable(GL10.GL_LINE_SMOOTH) |
||||||
|
gl.glDisable(GL10.GL_DEPTH_TEST) |
||||||
|
gl.glDisable(GL10.GL_CULL_FACE) |
||||||
|
|
||||||
|
mObserver.onSurfaceCreated() |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Removes CurlMesh from this renderer. |
||||||
|
*/ |
||||||
|
@Synchronized |
||||||
|
fun removeCurlMesh(mesh: CurlMesh) { |
||||||
|
mCurlMeshes.remove(mesh) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Change background/clear color. |
||||||
|
*/ |
||||||
|
fun setBackgroundColor(color: Int) { |
||||||
|
mBackgroundColor = color |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Set margins or padding. Note: margins are proportional. Meaning a value |
||||||
|
* of .1f will produce a 10% margin. |
||||||
|
*/ |
||||||
|
@Synchronized |
||||||
|
fun setMargins( |
||||||
|
left: Float, top: Float, right: Float, |
||||||
|
bottom: Float |
||||||
|
) { |
||||||
|
mMargins.left = left |
||||||
|
mMargins.top = top |
||||||
|
mMargins.right = right |
||||||
|
mMargins.bottom = bottom |
||||||
|
updatePageRects() |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets visible page count to one or two. Should be either SHOW_ONE_PAGE or |
||||||
|
* SHOW_TWO_PAGES. |
||||||
|
*/ |
||||||
|
@Synchronized |
||||||
|
fun setViewMode(viewmode: Int) { |
||||||
|
if (viewmode == SHOW_ONE_PAGE) { |
||||||
|
mViewMode = viewmode |
||||||
|
updatePageRects() |
||||||
|
} else if (viewmode == SHOW_TWO_PAGES) { |
||||||
|
mViewMode = viewmode |
||||||
|
updatePageRects() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Translates screen coordinates into view coordinates. |
||||||
|
*/ |
||||||
|
fun translate(pt: PointF) { |
||||||
|
pt.x = mViewRect.left + mViewRect.width() * pt.x / mViewportWidth |
||||||
|
pt.y = mViewRect.top - -mViewRect.height() * pt.y / mViewportHeight |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Recalculates page rectangles. |
||||||
|
*/ |
||||||
|
private fun updatePageRects() { |
||||||
|
if (mViewRect.width() == 0f || mViewRect.height() == 0f) { |
||||||
|
return |
||||||
|
} else if (mViewMode == SHOW_ONE_PAGE) { |
||||||
|
mPageRectRight.set(mViewRect) |
||||||
|
mPageRectRight.left += mViewRect.width() * mMargins.left |
||||||
|
mPageRectRight.right -= mViewRect.width() * mMargins.right |
||||||
|
mPageRectRight.top += mViewRect.height() * mMargins.top |
||||||
|
mPageRectRight.bottom -= mViewRect.height() * mMargins.bottom |
||||||
|
|
||||||
|
mPageRectLeft.set(mPageRectRight) |
||||||
|
mPageRectLeft.offset(-mPageRectRight.width(), 0f) |
||||||
|
|
||||||
|
val bitmapW = (mPageRectRight.width() * mViewportWidth / mViewRect |
||||||
|
.width()).toInt() |
||||||
|
val bitmapH = (mPageRectRight.height() * mViewportHeight / mViewRect |
||||||
|
.height()).toInt() |
||||||
|
mObserver.onPageSizeChanged(bitmapW, bitmapH) |
||||||
|
} else if (mViewMode == SHOW_TWO_PAGES) { |
||||||
|
mPageRectRight.set(mViewRect) |
||||||
|
mPageRectRight.left += mViewRect.width() * mMargins.left |
||||||
|
mPageRectRight.right -= mViewRect.width() * mMargins.right |
||||||
|
mPageRectRight.top += mViewRect.height() * mMargins.top |
||||||
|
mPageRectRight.bottom -= mViewRect.height() * mMargins.bottom |
||||||
|
|
||||||
|
mPageRectLeft.set(mPageRectRight) |
||||||
|
mPageRectLeft.right = (mPageRectLeft.right + mPageRectLeft.left) / 2 |
||||||
|
mPageRectRight.left = mPageRectLeft.right |
||||||
|
|
||||||
|
val bitmapW = (mPageRectRight.width() * mViewportWidth / mViewRect |
||||||
|
.width()).toInt() |
||||||
|
val bitmapH = (mPageRectRight.height() * mViewportHeight / mViewRect |
||||||
|
.height()).toInt() |
||||||
|
mObserver.onPageSizeChanged(bitmapW, bitmapH) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Observer for waiting render engine/state updates. |
||||||
|
*/ |
||||||
|
interface Observer { |
||||||
|
/** |
||||||
|
* Called from onDrawFrame called before rendering is started. This is |
||||||
|
* intended to be used for animation purposes. |
||||||
|
*/ |
||||||
|
fun onDrawFrame() |
||||||
|
|
||||||
|
/** |
||||||
|
* Called once page size is changed. Width and height tell the page size |
||||||
|
* in pixels making it possible to update textures accordingly. |
||||||
|
*/ |
||||||
|
fun onPageSizeChanged(width: Int, height: Int) |
||||||
|
|
||||||
|
/** |
||||||
|
* Called from onSurfaceCreated to enable texture re-initialization etc |
||||||
|
* what needs to be done when this happens. |
||||||
|
*/ |
||||||
|
fun onSurfaceCreated() |
||||||
|
} |
||||||
|
|
||||||
|
companion object { |
||||||
|
|
||||||
|
// Constant for requesting left page rect. |
||||||
|
const val PAGE_LEFT = 1 |
||||||
|
// Constant for requesting right page rect. |
||||||
|
const val PAGE_RIGHT = 2 |
||||||
|
// Constants for changing view mode. |
||||||
|
const val SHOW_ONE_PAGE = 1 |
||||||
|
const val SHOW_TWO_PAGES = 2 |
||||||
|
// Set to true for checking quickly how perspective projection looks. |
||||||
|
private const val USE_PERSPECTIVE_PROJECTION = false |
||||||
|
} |
||||||
|
} |
@ -1,790 +0,0 @@ |
|||||||
package io.legado.app.ui.widget.page.curl; |
|
||||||
|
|
||||||
import android.content.Context; |
|
||||||
import android.graphics.PixelFormat; |
|
||||||
import android.graphics.PointF; |
|
||||||
import android.graphics.RectF; |
|
||||||
import android.opengl.GLSurfaceView; |
|
||||||
import android.util.AttributeSet; |
|
||||||
import android.view.MotionEvent; |
|
||||||
import android.view.View; |
|
||||||
|
|
||||||
/** |
|
||||||
* OpenGL ES View. |
|
||||||
* |
|
||||||
* @author harism |
|
||||||
*/ |
|
||||||
public class CurlView extends GLSurfaceView implements View.OnTouchListener, |
|
||||||
CurlRenderer.Observer { |
|
||||||
|
|
||||||
// Curl state. We are flipping none, left or right page.
|
|
||||||
private static final int CURL_LEFT = 1; |
|
||||||
private static final int CURL_NONE = 0; |
|
||||||
private static final int CURL_RIGHT = 2; |
|
||||||
|
|
||||||
// Constants for mAnimationTargetEvent.
|
|
||||||
private static final int SET_CURL_TO_LEFT = 1; |
|
||||||
private static final int SET_CURL_TO_RIGHT = 2; |
|
||||||
|
|
||||||
// Shows one page at the center of view.
|
|
||||||
public static final int SHOW_ONE_PAGE = 1; |
|
||||||
// Shows two pages side by side.
|
|
||||||
public static final int SHOW_TWO_PAGES = 2; |
|
||||||
|
|
||||||
private boolean mAllowLastPageCurl = true; |
|
||||||
|
|
||||||
private boolean mAnimate = false; |
|
||||||
private long mAnimationDurationTime = 300; |
|
||||||
private PointF mAnimationSource = new PointF(); |
|
||||||
private long mAnimationStartTime; |
|
||||||
private PointF mAnimationTarget = new PointF(); |
|
||||||
private int mAnimationTargetEvent; |
|
||||||
|
|
||||||
private PointF mCurlDir = new PointF(); |
|
||||||
|
|
||||||
private PointF mCurlPos = new PointF(); |
|
||||||
private int mCurlState = CURL_NONE; |
|
||||||
// Current bitmap index. This is always showed as front of right page.
|
|
||||||
private int mCurrentIndex = 0; |
|
||||||
|
|
||||||
// Start position for dragging.
|
|
||||||
private PointF mDragStartPos = new PointF(); |
|
||||||
|
|
||||||
private boolean mEnableTouchPressure = false; |
|
||||||
// Bitmap size. These are updated from renderer once it's initialized.
|
|
||||||
private int mPageBitmapHeight = -1; |
|
||||||
|
|
||||||
private int mPageBitmapWidth = -1; |
|
||||||
// Page meshes. Left and right meshes are 'static' while curl is used to
|
|
||||||
// show page flipping.
|
|
||||||
private CurlMesh mPageCurl; |
|
||||||
|
|
||||||
private CurlMesh mPageLeft; |
|
||||||
private PageProvider mPageProvider; |
|
||||||
private CurlMesh mPageRight; |
|
||||||
|
|
||||||
private PointerPosition mPointerPos = new PointerPosition(); |
|
||||||
|
|
||||||
private CurlRenderer mRenderer; |
|
||||||
private boolean mRenderLeftPage = true; |
|
||||||
private SizeChangedObserver mSizeChangedObserver; |
|
||||||
|
|
||||||
// One page is the default.
|
|
||||||
private int mViewMode = SHOW_ONE_PAGE; |
|
||||||
|
|
||||||
/** |
|
||||||
* Default constructor. |
|
||||||
*/ |
|
||||||
public CurlView(Context ctx) { |
|
||||||
super(ctx); |
|
||||||
init(ctx); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Default constructor. |
|
||||||
*/ |
|
||||||
public CurlView(Context ctx, AttributeSet attrs) { |
|
||||||
super(ctx, attrs); |
|
||||||
init(ctx); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Default constructor. |
|
||||||
*/ |
|
||||||
public CurlView(Context ctx, AttributeSet attrs, int defStyle) { |
|
||||||
this(ctx, attrs); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Get current page index. Page indices are zero based values presenting |
|
||||||
* page being shown on right side of the book. |
|
||||||
*/ |
|
||||||
public int getCurrentIndex() { |
|
||||||
return mCurrentIndex; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Initialize method. |
|
||||||
*/ |
|
||||||
private void init(Context ctx) { |
|
||||||
setEGLConfigChooser(8, 8, 8, 8, 16, 0); |
|
||||||
getHolder().setFormat(PixelFormat.TRANSLUCENT); |
|
||||||
setZOrderOnTop(true); |
|
||||||
mRenderer = new CurlRenderer(this); |
|
||||||
setRenderer(mRenderer); |
|
||||||
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); |
|
||||||
setOnTouchListener(this); |
|
||||||
|
|
||||||
// Even though left and right pages are static we have to allocate room
|
|
||||||
// for curl on them too as we are switching meshes. Another way would be
|
|
||||||
// to swap texture ids only.
|
|
||||||
mPageLeft = new CurlMesh(10); |
|
||||||
mPageRight = new CurlMesh(10); |
|
||||||
mPageCurl = new CurlMesh(10); |
|
||||||
mPageLeft.setFlipTexture(true); |
|
||||||
mPageRight.setFlipTexture(false); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onDrawFrame() { |
|
||||||
// We are not animating.
|
|
||||||
if (mAnimate == false) { |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
long currentTime = System.currentTimeMillis(); |
|
||||||
// If animation is done.
|
|
||||||
if (currentTime >= mAnimationStartTime + mAnimationDurationTime) { |
|
||||||
if (mAnimationTargetEvent == SET_CURL_TO_RIGHT) { |
|
||||||
// Switch curled page to right.
|
|
||||||
CurlMesh right = mPageCurl; |
|
||||||
CurlMesh curl = mPageRight; |
|
||||||
right.setRect(mRenderer.getPageRect(CurlRenderer.PAGE_RIGHT)); |
|
||||||
right.setFlipTexture(false); |
|
||||||
right.reset(); |
|
||||||
mRenderer.removeCurlMesh(curl); |
|
||||||
mPageCurl = curl; |
|
||||||
mPageRight = right; |
|
||||||
// If we were curling left page update current index.
|
|
||||||
if (mCurlState == CURL_LEFT) { |
|
||||||
--mCurrentIndex; |
|
||||||
} |
|
||||||
} else if (mAnimationTargetEvent == SET_CURL_TO_LEFT) { |
|
||||||
// Switch curled page to left.
|
|
||||||
CurlMesh left = mPageCurl; |
|
||||||
CurlMesh curl = mPageLeft; |
|
||||||
left.setRect(mRenderer.getPageRect(CurlRenderer.PAGE_LEFT)); |
|
||||||
left.setFlipTexture(true); |
|
||||||
left.reset(); |
|
||||||
mRenderer.removeCurlMesh(curl); |
|
||||||
if (!mRenderLeftPage) { |
|
||||||
mRenderer.removeCurlMesh(left); |
|
||||||
} |
|
||||||
mPageCurl = curl; |
|
||||||
mPageLeft = left; |
|
||||||
// If we were curling right page update current index.
|
|
||||||
if (mCurlState == CURL_RIGHT) { |
|
||||||
++mCurrentIndex; |
|
||||||
} |
|
||||||
} |
|
||||||
mCurlState = CURL_NONE; |
|
||||||
mAnimate = false; |
|
||||||
requestRender(); |
|
||||||
} else { |
|
||||||
mPointerPos.mPos.set(mAnimationSource); |
|
||||||
float t = 1f - ((float) (currentTime - mAnimationStartTime) / mAnimationDurationTime); |
|
||||||
t = 1f - (t * t * t * (3 - 2 * t)); |
|
||||||
mPointerPos.mPos.x += (mAnimationTarget.x - mAnimationSource.x) * t; |
|
||||||
mPointerPos.mPos.y += (mAnimationTarget.y - mAnimationSource.y) * t; |
|
||||||
updateCurlPos(mPointerPos); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onPageSizeChanged(int width, int height) { |
|
||||||
mPageBitmapWidth = width; |
|
||||||
mPageBitmapHeight = height; |
|
||||||
updatePages(); |
|
||||||
requestRender(); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onSizeChanged(int w, int h, int ow, int oh) { |
|
||||||
super.onSizeChanged(w, h, ow, oh); |
|
||||||
requestRender(); |
|
||||||
if (mSizeChangedObserver != null) { |
|
||||||
mSizeChangedObserver.onSizeChanged(w, h); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onSurfaceCreated() { |
|
||||||
// In case surface is recreated, let page meshes drop allocated texture
|
|
||||||
// ids and ask for new ones. There's no need to set textures here as
|
|
||||||
// onPageSizeChanged should be called later on.
|
|
||||||
mPageLeft.resetTexture(); |
|
||||||
mPageRight.resetTexture(); |
|
||||||
mPageCurl.resetTexture(); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public boolean onTouch(View view, MotionEvent me) { |
|
||||||
// No dragging during animation at the moment.
|
|
||||||
// TODO: Stop animation on touch event and return to drag mode.
|
|
||||||
if (mAnimate || mPageProvider == null) { |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
// We need page rects quite extensively so get them for later use.
|
|
||||||
RectF rightRect = mRenderer.getPageRect(CurlRenderer.PAGE_RIGHT); |
|
||||||
RectF leftRect = mRenderer.getPageRect(CurlRenderer.PAGE_LEFT); |
|
||||||
|
|
||||||
// Store pointer position.
|
|
||||||
mPointerPos.mPos.set(me.getX(), me.getY()); |
|
||||||
mRenderer.translate(mPointerPos.mPos); |
|
||||||
if (mEnableTouchPressure) { |
|
||||||
mPointerPos.mPressure = me.getPressure(); |
|
||||||
} else { |
|
||||||
mPointerPos.mPressure = 0.8f; |
|
||||||
} |
|
||||||
|
|
||||||
switch (me.getAction()) { |
|
||||||
case MotionEvent.ACTION_DOWN: { |
|
||||||
|
|
||||||
// Once we receive pointer down event its position is mapped to
|
|
||||||
// right or left edge of page and that'll be the position from where
|
|
||||||
// user is holding the paper to make curl happen.
|
|
||||||
mDragStartPos.set(mPointerPos.mPos); |
|
||||||
|
|
||||||
// First we make sure it's not over or below page. Pages are
|
|
||||||
// supposed to be same height so it really doesn't matter do we use
|
|
||||||
// left or right one.
|
|
||||||
if (mDragStartPos.y > rightRect.top) { |
|
||||||
mDragStartPos.y = rightRect.top; |
|
||||||
} else if (mDragStartPos.y < rightRect.bottom) { |
|
||||||
mDragStartPos.y = rightRect.bottom; |
|
||||||
} |
|
||||||
|
|
||||||
// Then we have to make decisions for the user whether curl is going
|
|
||||||
// to happen from left or right, and on which page.
|
|
||||||
if (mViewMode == SHOW_TWO_PAGES) { |
|
||||||
// If we have an open book and pointer is on the left from right
|
|
||||||
// page we'll mark drag position to left edge of left page.
|
|
||||||
// Additionally checking mCurrentIndex is higher than zero tells
|
|
||||||
// us there is a visible page at all.
|
|
||||||
if (mDragStartPos.x < rightRect.left && mCurrentIndex > 0) { |
|
||||||
mDragStartPos.x = leftRect.left; |
|
||||||
startCurl(CURL_LEFT); |
|
||||||
} |
|
||||||
// Otherwise check pointer is on right page's side.
|
|
||||||
else if (mDragStartPos.x >= rightRect.left |
|
||||||
&& mCurrentIndex < mPageProvider.getPageCount()) { |
|
||||||
mDragStartPos.x = rightRect.right; |
|
||||||
if (!mAllowLastPageCurl |
|
||||||
&& mCurrentIndex >= mPageProvider.getPageCount() - 1) { |
|
||||||
return false; |
|
||||||
} |
|
||||||
startCurl(CURL_RIGHT); |
|
||||||
} |
|
||||||
} else if (mViewMode == SHOW_ONE_PAGE) { |
|
||||||
float halfX = (rightRect.right + rightRect.left) / 2; |
|
||||||
if (mDragStartPos.x < halfX && mCurrentIndex > 0) { |
|
||||||
mDragStartPos.x = rightRect.left; |
|
||||||
startCurl(CURL_LEFT); |
|
||||||
} else if (mDragStartPos.x >= halfX |
|
||||||
&& mCurrentIndex < mPageProvider.getPageCount()) { |
|
||||||
mDragStartPos.x = rightRect.right; |
|
||||||
if (!mAllowLastPageCurl |
|
||||||
&& mCurrentIndex >= mPageProvider.getPageCount() - 1) { |
|
||||||
return false; |
|
||||||
} |
|
||||||
startCurl(CURL_RIGHT); |
|
||||||
} |
|
||||||
} |
|
||||||
// If we have are in curl state, let this case clause flow through
|
|
||||||
// to next one. We have pointer position and drag position defined
|
|
||||||
// and this will create first render request given these points.
|
|
||||||
if (mCurlState == CURL_NONE) { |
|
||||||
return false; |
|
||||||
} |
|
||||||
} |
|
||||||
case MotionEvent.ACTION_MOVE: { |
|
||||||
updateCurlPos(mPointerPos); |
|
||||||
break; |
|
||||||
} |
|
||||||
case MotionEvent.ACTION_CANCEL: |
|
||||||
case MotionEvent.ACTION_UP: { |
|
||||||
if (mCurlState == CURL_LEFT || mCurlState == CURL_RIGHT) { |
|
||||||
// Animation source is the point from where animation starts.
|
|
||||||
// Also it's handled in a way we actually simulate touch events
|
|
||||||
// meaning the output is exactly the same as if user drags the
|
|
||||||
// page to other side. While not producing the best looking
|
|
||||||
// result (which is easier done by altering curl position and/or
|
|
||||||
// direction directly), this is done in a hope it made code a
|
|
||||||
// bit more readable and easier to maintain.
|
|
||||||
mAnimationSource.set(mPointerPos.mPos); |
|
||||||
mAnimationStartTime = System.currentTimeMillis(); |
|
||||||
|
|
||||||
// Given the explanation, here we decide whether to simulate
|
|
||||||
// drag to left or right end.
|
|
||||||
if ((mViewMode == SHOW_ONE_PAGE && mPointerPos.mPos.x > (rightRect.left + rightRect.right) / 2) |
|
||||||
|| mViewMode == SHOW_TWO_PAGES |
|
||||||
&& mPointerPos.mPos.x > rightRect.left) { |
|
||||||
// On right side target is always right page's right border.
|
|
||||||
mAnimationTarget.set(mDragStartPos); |
|
||||||
mAnimationTarget.x = mRenderer |
|
||||||
.getPageRect(CurlRenderer.PAGE_RIGHT).right; |
|
||||||
mAnimationTargetEvent = SET_CURL_TO_RIGHT; |
|
||||||
} else { |
|
||||||
// On left side target depends on visible pages.
|
|
||||||
mAnimationTarget.set(mDragStartPos); |
|
||||||
if (mCurlState == CURL_RIGHT || mViewMode == SHOW_TWO_PAGES) { |
|
||||||
mAnimationTarget.x = leftRect.left; |
|
||||||
} else { |
|
||||||
mAnimationTarget.x = rightRect.left; |
|
||||||
} |
|
||||||
mAnimationTargetEvent = SET_CURL_TO_LEFT; |
|
||||||
} |
|
||||||
mAnimate = true; |
|
||||||
requestRender(); |
|
||||||
} |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Allow the last page to curl. |
|
||||||
*/ |
|
||||||
public void setAllowLastPageCurl(boolean allowLastPageCurl) { |
|
||||||
mAllowLastPageCurl = allowLastPageCurl; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Sets mPageCurl curl position. |
|
||||||
*/ |
|
||||||
private void setCurlPos(PointF curlPos, PointF curlDir, double radius) { |
|
||||||
|
|
||||||
// First reposition curl so that page doesn't 'rip off' from book.
|
|
||||||
if (mCurlState == CURL_RIGHT |
|
||||||
|| (mCurlState == CURL_LEFT && mViewMode == SHOW_ONE_PAGE)) { |
|
||||||
RectF pageRect = mRenderer.getPageRect(CurlRenderer.PAGE_RIGHT); |
|
||||||
if (curlPos.x >= pageRect.right) { |
|
||||||
mPageCurl.reset(); |
|
||||||
requestRender(); |
|
||||||
return; |
|
||||||
} |
|
||||||
if (curlPos.x < pageRect.left) { |
|
||||||
curlPos.x = pageRect.left; |
|
||||||
} |
|
||||||
if (curlDir.y != 0) { |
|
||||||
float diffX = curlPos.x - pageRect.left; |
|
||||||
float leftY = curlPos.y + (diffX * curlDir.x / curlDir.y); |
|
||||||
if (curlDir.y < 0 && leftY < pageRect.top) { |
|
||||||
curlDir.x = curlPos.y - pageRect.top; |
|
||||||
curlDir.y = pageRect.left - curlPos.x; |
|
||||||
} else if (curlDir.y > 0 && leftY > pageRect.bottom) { |
|
||||||
curlDir.x = pageRect.bottom - curlPos.y; |
|
||||||
curlDir.y = curlPos.x - pageRect.left; |
|
||||||
} |
|
||||||
} |
|
||||||
} else if (mCurlState == CURL_LEFT) { |
|
||||||
RectF pageRect = mRenderer.getPageRect(CurlRenderer.PAGE_LEFT); |
|
||||||
if (curlPos.x <= pageRect.left) { |
|
||||||
mPageCurl.reset(); |
|
||||||
requestRender(); |
|
||||||
return; |
|
||||||
} |
|
||||||
if (curlPos.x > pageRect.right) { |
|
||||||
curlPos.x = pageRect.right; |
|
||||||
} |
|
||||||
if (curlDir.y != 0) { |
|
||||||
float diffX = curlPos.x - pageRect.right; |
|
||||||
float rightY = curlPos.y + (diffX * curlDir.x / curlDir.y); |
|
||||||
if (curlDir.y < 0 && rightY < pageRect.top) { |
|
||||||
curlDir.x = pageRect.top - curlPos.y; |
|
||||||
curlDir.y = curlPos.x - pageRect.right; |
|
||||||
} else if (curlDir.y > 0 && rightY > pageRect.bottom) { |
|
||||||
curlDir.x = curlPos.y - pageRect.bottom; |
|
||||||
curlDir.y = pageRect.right - curlPos.x; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Finally normalize direction vector and do rendering.
|
|
||||||
double dist = Math.sqrt(curlDir.x * curlDir.x + curlDir.y * curlDir.y); |
|
||||||
if (dist != 0) { |
|
||||||
curlDir.x /= dist; |
|
||||||
curlDir.y /= dist; |
|
||||||
mPageCurl.curl(curlPos, curlDir, radius); |
|
||||||
} else { |
|
||||||
mPageCurl.reset(); |
|
||||||
} |
|
||||||
|
|
||||||
requestRender(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Set current page index. Page indices are zero based values presenting |
|
||||||
* page being shown on right side of the book. E.g if you set value to 4; |
|
||||||
* right side front facing bitmap will be with index 4, back facing 5 and |
|
||||||
* for left side page index 3 is front facing, and index 2 back facing (once |
|
||||||
* page is on left side it's flipped over). |
|
||||||
* <p> |
|
||||||
* Current index is rounded to closest value divisible with 2. |
|
||||||
*/ |
|
||||||
public void setCurrentIndex(int index) { |
|
||||||
if (mPageProvider == null || index < 0) { |
|
||||||
mCurrentIndex = 0; |
|
||||||
} else { |
|
||||||
if (mAllowLastPageCurl) { |
|
||||||
mCurrentIndex = Math.min(index, mPageProvider.getPageCount()); |
|
||||||
} else { |
|
||||||
mCurrentIndex = Math.min(index, |
|
||||||
mPageProvider.getPageCount() - 1); |
|
||||||
} |
|
||||||
} |
|
||||||
updatePages(); |
|
||||||
requestRender(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* If set to true, touch event pressure information is used to adjust curl |
|
||||||
* radius. The more you press, the flatter the curl becomes. This is |
|
||||||
* somewhat experimental and results may vary significantly between devices. |
|
||||||
* On emulator pressure information seems to be flat 1.0f which is maximum |
|
||||||
* value and therefore not very much of use. |
|
||||||
*/ |
|
||||||
public void setEnableTouchPressure(boolean enableTouchPressure) { |
|
||||||
mEnableTouchPressure = enableTouchPressure; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Set margins (or padding). Note: margins are proportional. Meaning a value |
|
||||||
* of .1f will produce a 10% margin. |
|
||||||
*/ |
|
||||||
public void setMargins(float left, float top, float right, float bottom) { |
|
||||||
mRenderer.setMargins(left, top, right, bottom); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Update/set page provider. |
|
||||||
*/ |
|
||||||
public void setPageProvider(PageProvider pageProvider) { |
|
||||||
mPageProvider = pageProvider; |
|
||||||
mCurrentIndex = 0; |
|
||||||
updatePages(); |
|
||||||
requestRender(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Setter for whether left side page is rendered. This is useful mostly for |
|
||||||
* situations where right (main) page is aligned to left side of screen and |
|
||||||
* left page is not visible anyway. |
|
||||||
*/ |
|
||||||
public void setRenderLeftPage(boolean renderLeftPage) { |
|
||||||
mRenderLeftPage = renderLeftPage; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Sets SizeChangedObserver for this View. Call back method is called from |
|
||||||
* this View's onSizeChanged method. |
|
||||||
*/ |
|
||||||
public void setSizeChangedObserver(SizeChangedObserver observer) { |
|
||||||
mSizeChangedObserver = observer; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Sets view mode. Value can be either SHOW_ONE_PAGE or SHOW_TWO_PAGES. In |
|
||||||
* former case right page is made size of display, and in latter case two |
|
||||||
* pages are laid on visible area. |
|
||||||
*/ |
|
||||||
public void setViewMode(int viewMode) { |
|
||||||
switch (viewMode) { |
|
||||||
case SHOW_ONE_PAGE: |
|
||||||
mViewMode = viewMode; |
|
||||||
mPageLeft.setFlipTexture(true); |
|
||||||
mRenderer.setViewMode(CurlRenderer.SHOW_ONE_PAGE); |
|
||||||
break; |
|
||||||
case SHOW_TWO_PAGES: |
|
||||||
mViewMode = viewMode; |
|
||||||
mPageLeft.setFlipTexture(false); |
|
||||||
mRenderer.setViewMode(CurlRenderer.SHOW_TWO_PAGES); |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Switches meshes and loads new bitmaps if available. Updated to support 2 |
|
||||||
* pages in landscape |
|
||||||
*/ |
|
||||||
private void startCurl(int page) { |
|
||||||
switch (page) { |
|
||||||
|
|
||||||
// Once right side page is curled, first right page is assigned into
|
|
||||||
// curled page. And if there are more bitmaps available new bitmap is
|
|
||||||
// loaded into right side mesh.
|
|
||||||
case CURL_RIGHT: { |
|
||||||
// Remove meshes from renderer.
|
|
||||||
mRenderer.removeCurlMesh(mPageLeft); |
|
||||||
mRenderer.removeCurlMesh(mPageRight); |
|
||||||
mRenderer.removeCurlMesh(mPageCurl); |
|
||||||
|
|
||||||
// We are curling right page.
|
|
||||||
CurlMesh curl = mPageRight; |
|
||||||
mPageRight = mPageCurl; |
|
||||||
mPageCurl = curl; |
|
||||||
|
|
||||||
if (mCurrentIndex > 0) { |
|
||||||
mPageLeft.setFlipTexture(true); |
|
||||||
mPageLeft |
|
||||||
.setRect(mRenderer.getPageRect(CurlRenderer.PAGE_LEFT)); |
|
||||||
mPageLeft.reset(); |
|
||||||
if (mRenderLeftPage) { |
|
||||||
mRenderer.addCurlMesh(mPageLeft); |
|
||||||
} |
|
||||||
} |
|
||||||
if (mCurrentIndex < mPageProvider.getPageCount() - 1) { |
|
||||||
updatePage(mPageRight.getTexturePage(), mCurrentIndex + 1); |
|
||||||
mPageRight.setRect(mRenderer |
|
||||||
.getPageRect(CurlRenderer.PAGE_RIGHT)); |
|
||||||
mPageRight.setFlipTexture(false); |
|
||||||
mPageRight.reset(); |
|
||||||
mRenderer.addCurlMesh(mPageRight); |
|
||||||
} |
|
||||||
|
|
||||||
// Add curled page to renderer.
|
|
||||||
mPageCurl.setRect(mRenderer.getPageRect(CurlRenderer.PAGE_RIGHT)); |
|
||||||
mPageCurl.setFlipTexture(false); |
|
||||||
mPageCurl.reset(); |
|
||||||
mRenderer.addCurlMesh(mPageCurl); |
|
||||||
|
|
||||||
mCurlState = CURL_RIGHT; |
|
||||||
break; |
|
||||||
} |
|
||||||
|
|
||||||
// On left side curl, left page is assigned to curled page. And if
|
|
||||||
// there are more bitmaps available before currentIndex, new bitmap
|
|
||||||
// is loaded into left page.
|
|
||||||
case CURL_LEFT: { |
|
||||||
// Remove meshes from renderer.
|
|
||||||
mRenderer.removeCurlMesh(mPageLeft); |
|
||||||
mRenderer.removeCurlMesh(mPageRight); |
|
||||||
mRenderer.removeCurlMesh(mPageCurl); |
|
||||||
|
|
||||||
// We are curling left page.
|
|
||||||
CurlMesh curl = mPageLeft; |
|
||||||
mPageLeft = mPageCurl; |
|
||||||
mPageCurl = curl; |
|
||||||
|
|
||||||
if (mCurrentIndex > 1) { |
|
||||||
updatePage(mPageLeft.getTexturePage(), mCurrentIndex - 2); |
|
||||||
mPageLeft.setFlipTexture(true); |
|
||||||
mPageLeft |
|
||||||
.setRect(mRenderer.getPageRect(CurlRenderer.PAGE_LEFT)); |
|
||||||
mPageLeft.reset(); |
|
||||||
if (mRenderLeftPage) { |
|
||||||
mRenderer.addCurlMesh(mPageLeft); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// If there is something to show on right page add it to renderer.
|
|
||||||
if (mCurrentIndex < mPageProvider.getPageCount()) { |
|
||||||
mPageRight.setFlipTexture(false); |
|
||||||
mPageRight.setRect(mRenderer |
|
||||||
.getPageRect(CurlRenderer.PAGE_RIGHT)); |
|
||||||
mPageRight.reset(); |
|
||||||
mRenderer.addCurlMesh(mPageRight); |
|
||||||
} |
|
||||||
|
|
||||||
// How dragging previous page happens depends on view mode.
|
|
||||||
if (mViewMode == SHOW_ONE_PAGE |
|
||||||
|| (mCurlState == CURL_LEFT && mViewMode == SHOW_TWO_PAGES)) { |
|
||||||
mPageCurl.setRect(mRenderer |
|
||||||
.getPageRect(CurlRenderer.PAGE_RIGHT)); |
|
||||||
mPageCurl.setFlipTexture(false); |
|
||||||
} else { |
|
||||||
mPageCurl |
|
||||||
.setRect(mRenderer.getPageRect(CurlRenderer.PAGE_LEFT)); |
|
||||||
mPageCurl.setFlipTexture(true); |
|
||||||
} |
|
||||||
mPageCurl.reset(); |
|
||||||
mRenderer.addCurlMesh(mPageCurl); |
|
||||||
|
|
||||||
mCurlState = CURL_LEFT; |
|
||||||
break; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Updates curl position. |
|
||||||
*/ |
|
||||||
private void updateCurlPos(PointerPosition pointerPos) { |
|
||||||
|
|
||||||
// Default curl radius.
|
|
||||||
double radius = mRenderer.getPageRect(CURL_RIGHT).width() / 3; |
|
||||||
// TODO: This is not an optimal solution. Based on feedback received so
|
|
||||||
// far; pressure is not very accurate, it may be better not to map
|
|
||||||
// coefficient to range [0f, 1f] but something like [.2f, 1f] instead.
|
|
||||||
// Leaving it as is until get my hands on a real device. On emulator
|
|
||||||
// this doesn't work anyway.
|
|
||||||
radius *= Math.max(1f - pointerPos.mPressure, 0f); |
|
||||||
// NOTE: Here we set pointerPos to mCurlPos. It might be a bit confusing
|
|
||||||
// later to see e.g "mCurlPos.x - mDragStartPos.x" used. But it's
|
|
||||||
// actually pointerPos we are doing calculations against. Why? Simply to
|
|
||||||
// optimize code a bit with the cost of making it unreadable. Otherwise
|
|
||||||
// we had to this in both of the next if-else branches.
|
|
||||||
mCurlPos.set(pointerPos.mPos); |
|
||||||
|
|
||||||
// If curl happens on right page, or on left page on two page mode,
|
|
||||||
// we'll calculate curl position from pointerPos.
|
|
||||||
if (mCurlState == CURL_RIGHT |
|
||||||
|| (mCurlState == CURL_LEFT && mViewMode == SHOW_TWO_PAGES)) { |
|
||||||
|
|
||||||
mCurlDir.x = mCurlPos.x - mDragStartPos.x; |
|
||||||
mCurlDir.y = mCurlPos.y - mDragStartPos.y; |
|
||||||
float dist = (float) Math.sqrt(mCurlDir.x * mCurlDir.x + mCurlDir.y |
|
||||||
* mCurlDir.y); |
|
||||||
|
|
||||||
// Adjust curl radius so that if page is dragged far enough on
|
|
||||||
// opposite side, radius gets closer to zero.
|
|
||||||
float pageWidth = mRenderer.getPageRect(CurlRenderer.PAGE_RIGHT) |
|
||||||
.width(); |
|
||||||
double curlLen = radius * Math.PI; |
|
||||||
if (dist > (pageWidth * 2) - curlLen) { |
|
||||||
curlLen = Math.max((pageWidth * 2) - dist, 0f); |
|
||||||
radius = curlLen / Math.PI; |
|
||||||
} |
|
||||||
|
|
||||||
// Actual curl position calculation.
|
|
||||||
if (dist >= curlLen) { |
|
||||||
double translate = (dist - curlLen) / 2; |
|
||||||
if (mViewMode == SHOW_TWO_PAGES) { |
|
||||||
mCurlPos.x -= mCurlDir.x * translate / dist; |
|
||||||
} else { |
|
||||||
float pageLeftX = mRenderer |
|
||||||
.getPageRect(CurlRenderer.PAGE_RIGHT).left; |
|
||||||
radius = Math.max(Math.min(mCurlPos.x - pageLeftX, radius), |
|
||||||
0f); |
|
||||||
} |
|
||||||
mCurlPos.y -= mCurlDir.y * translate / dist; |
|
||||||
} else { |
|
||||||
double angle = Math.PI * Math.sqrt(dist / curlLen); |
|
||||||
double translate = radius * Math.sin(angle); |
|
||||||
mCurlPos.x += mCurlDir.x * translate / dist; |
|
||||||
mCurlPos.y += mCurlDir.y * translate / dist; |
|
||||||
} |
|
||||||
} |
|
||||||
// Otherwise we'll let curl follow pointer position.
|
|
||||||
else if (mCurlState == CURL_LEFT) { |
|
||||||
|
|
||||||
// Adjust radius regarding how close to page edge we are.
|
|
||||||
float pageLeftX = mRenderer.getPageRect(CurlRenderer.PAGE_RIGHT).left; |
|
||||||
radius = Math.max(Math.min(mCurlPos.x - pageLeftX, radius), 0f); |
|
||||||
|
|
||||||
float pageRightX = mRenderer.getPageRect(CurlRenderer.PAGE_RIGHT).right; |
|
||||||
mCurlPos.x -= Math.min(pageRightX - mCurlPos.x, radius); |
|
||||||
mCurlDir.x = mCurlPos.x + mDragStartPos.x; |
|
||||||
mCurlDir.y = mCurlPos.y - mDragStartPos.y; |
|
||||||
} |
|
||||||
|
|
||||||
setCurlPos(mCurlPos, mCurlDir, radius); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Updates given CurlPage via PageProvider for page located at index. |
|
||||||
*/ |
|
||||||
private void updatePage(CurlPage page, int index) { |
|
||||||
// First reset page to initial state.
|
|
||||||
page.reset(); |
|
||||||
// Ask page provider to fill it up with bitmaps and colors.
|
|
||||||
mPageProvider.updatePage(page, mPageBitmapWidth, mPageBitmapHeight, |
|
||||||
index); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Updates bitmaps for page meshes. |
|
||||||
*/ |
|
||||||
private void updatePages() { |
|
||||||
if (mPageProvider == null || mPageBitmapWidth <= 0 |
|
||||||
|| mPageBitmapHeight <= 0) { |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
// Remove meshes from renderer.
|
|
||||||
mRenderer.removeCurlMesh(mPageLeft); |
|
||||||
mRenderer.removeCurlMesh(mPageRight); |
|
||||||
mRenderer.removeCurlMesh(mPageCurl); |
|
||||||
|
|
||||||
int leftIdx = mCurrentIndex - 1; |
|
||||||
int rightIdx = mCurrentIndex; |
|
||||||
int curlIdx = -1; |
|
||||||
if (mCurlState == CURL_LEFT) { |
|
||||||
curlIdx = leftIdx; |
|
||||||
--leftIdx; |
|
||||||
} else if (mCurlState == CURL_RIGHT) { |
|
||||||
curlIdx = rightIdx; |
|
||||||
++rightIdx; |
|
||||||
} |
|
||||||
|
|
||||||
if (rightIdx >= 0 && rightIdx < mPageProvider.getPageCount()) { |
|
||||||
updatePage(mPageRight.getTexturePage(), rightIdx); |
|
||||||
mPageRight.setFlipTexture(false); |
|
||||||
mPageRight.setRect(mRenderer.getPageRect(CurlRenderer.PAGE_RIGHT)); |
|
||||||
mPageRight.reset(); |
|
||||||
mRenderer.addCurlMesh(mPageRight); |
|
||||||
} |
|
||||||
if (leftIdx >= 0 && leftIdx < mPageProvider.getPageCount()) { |
|
||||||
updatePage(mPageLeft.getTexturePage(), leftIdx); |
|
||||||
mPageLeft.setFlipTexture(true); |
|
||||||
mPageLeft.setRect(mRenderer.getPageRect(CurlRenderer.PAGE_LEFT)); |
|
||||||
mPageLeft.reset(); |
|
||||||
if (mRenderLeftPage) { |
|
||||||
mRenderer.addCurlMesh(mPageLeft); |
|
||||||
} |
|
||||||
} |
|
||||||
if (curlIdx >= 0 && curlIdx < mPageProvider.getPageCount()) { |
|
||||||
updatePage(mPageCurl.getTexturePage(), curlIdx); |
|
||||||
|
|
||||||
if (mCurlState == CURL_RIGHT) { |
|
||||||
mPageCurl.setFlipTexture(true); |
|
||||||
mPageCurl.setRect(mRenderer |
|
||||||
.getPageRect(CurlRenderer.PAGE_RIGHT)); |
|
||||||
} else { |
|
||||||
mPageCurl.setFlipTexture(false); |
|
||||||
mPageCurl |
|
||||||
.setRect(mRenderer.getPageRect(CurlRenderer.PAGE_LEFT)); |
|
||||||
} |
|
||||||
|
|
||||||
mPageCurl.reset(); |
|
||||||
mRenderer.addCurlMesh(mPageCurl); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Provider for feeding 'book' with bitmaps which are used for rendering |
|
||||||
* pages. |
|
||||||
*/ |
|
||||||
public interface PageProvider { |
|
||||||
|
|
||||||
/** |
|
||||||
* Return number of pages available. |
|
||||||
*/ |
|
||||||
public int getPageCount(); |
|
||||||
|
|
||||||
/** |
|
||||||
* Called once new bitmaps/textures are needed. Width and height are in |
|
||||||
* pixels telling the size it will be drawn on screen and following them |
|
||||||
* ensures that aspect ratio remains. But it's possible to return bitmap |
|
||||||
* of any size though. You should use provided CurlPage for storing page |
|
||||||
* information for requested page number.<br/> |
|
||||||
* <br/> |
|
||||||
* Index is a number between 0 and getBitmapCount() - 1. |
|
||||||
*/ |
|
||||||
public void updatePage(CurlPage page, int width, int height, int index); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Simple holder for pointer position. |
|
||||||
*/ |
|
||||||
private class PointerPosition { |
|
||||||
PointF mPos = new PointF(); |
|
||||||
float mPressure; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Observer interface for handling CurlView size changes. |
|
||||||
*/ |
|
||||||
public interface SizeChangedObserver { |
|
||||||
|
|
||||||
/** |
|
||||||
* Called once CurlView size changes. |
|
||||||
*/ |
|
||||||
public void onSizeChanged(int width, int height); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -0,0 +1,768 @@ |
|||||||
|
package io.legado.app.ui.widget.page.curl |
||||||
|
|
||||||
|
import android.content.Context |
||||||
|
import android.graphics.PointF |
||||||
|
import android.opengl.GLSurfaceView |
||||||
|
import android.util.AttributeSet |
||||||
|
import android.view.MotionEvent |
||||||
|
import android.view.View |
||||||
|
import kotlin.math.max |
||||||
|
import kotlin.math.min |
||||||
|
import kotlin.math.sin |
||||||
|
import kotlin.math.sqrt |
||||||
|
|
||||||
|
/** |
||||||
|
* OpenGL ES View. |
||||||
|
* |
||||||
|
* @author harism |
||||||
|
*/ |
||||||
|
class CurlView : GLSurfaceView, View.OnTouchListener, CurlRenderer.Observer { |
||||||
|
|
||||||
|
private var mAllowLastPageCurl = true |
||||||
|
|
||||||
|
private var mAnimate = false |
||||||
|
private val mAnimationDurationTime: Long = 300 |
||||||
|
private val mAnimationSource = PointF() |
||||||
|
private var mAnimationStartTime: Long = 0 |
||||||
|
private val mAnimationTarget = PointF() |
||||||
|
private var mAnimationTargetEvent: Int = 0 |
||||||
|
|
||||||
|
private val mCurlDir = PointF() |
||||||
|
|
||||||
|
private val mCurlPos = PointF() |
||||||
|
private var mCurlState = CURL_NONE |
||||||
|
// Current bitmap index. This is always showed as front of right page. |
||||||
|
private var mCurrentIndex = 0 |
||||||
|
|
||||||
|
// Start position for dragging. |
||||||
|
private val mDragStartPos = PointF() |
||||||
|
|
||||||
|
private var mEnableTouchPressure = false |
||||||
|
// Bitmap size. These are updated from renderer once it's initialized. |
||||||
|
private var mPageBitmapHeight = -1 |
||||||
|
|
||||||
|
private var mPageBitmapWidth = -1 |
||||||
|
// Page meshes. Left and right meshes are 'static' while curl is used to |
||||||
|
// show page flipping. |
||||||
|
private var mPageCurl: CurlMesh |
||||||
|
private var mPageLeft: CurlMesh |
||||||
|
private var mPageRight: CurlMesh |
||||||
|
|
||||||
|
private var mPageProvider: PageProvider? = null |
||||||
|
|
||||||
|
private val mPointerPos = PointerPosition() |
||||||
|
|
||||||
|
private var mRenderer: CurlRenderer = CurlRenderer(this) |
||||||
|
private var mRenderLeftPage = true |
||||||
|
private var mSizeChangedObserver: SizeChangedObserver? = null |
||||||
|
|
||||||
|
// One page is the default. |
||||||
|
private var mViewMode = SHOW_ONE_PAGE |
||||||
|
|
||||||
|
/** |
||||||
|
* Get current page index. Page indices are zero based values presenting |
||||||
|
* page being shown on right side of the book. |
||||||
|
*/ |
||||||
|
/** |
||||||
|
* Set current page index. Page indices are zero based values presenting |
||||||
|
* page being shown on right side of the book. E.g if you set value to 4; |
||||||
|
* right side front facing bitmap will be with index 4, back facing 5 and |
||||||
|
* for left side page index 3 is front facing, and index 2 back facing (once |
||||||
|
* page is on left side it's flipped over). |
||||||
|
* |
||||||
|
* |
||||||
|
* Current index is rounded to closest value divisible with 2. |
||||||
|
*/ |
||||||
|
var currentIndex: Int |
||||||
|
get() = mCurrentIndex |
||||||
|
set(index) { |
||||||
|
mCurrentIndex = if (mPageProvider == null || index < 0) { |
||||||
|
0 |
||||||
|
} else { |
||||||
|
if (mAllowLastPageCurl) { |
||||||
|
min(index, mPageProvider!!.pageCount) |
||||||
|
} else { |
||||||
|
min( |
||||||
|
index, |
||||||
|
mPageProvider!!.pageCount - 1 |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
updatePages() |
||||||
|
requestRender() |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Default constructor. |
||||||
|
*/ |
||||||
|
constructor(ctx: Context) : super(ctx) |
||||||
|
|
||||||
|
/** |
||||||
|
* Default constructor. |
||||||
|
*/ |
||||||
|
constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs) |
||||||
|
|
||||||
|
/** |
||||||
|
* Default constructor. |
||||||
|
*/ |
||||||
|
constructor(ctx: Context, attrs: AttributeSet, defStyle: Int) : this(ctx, attrs) |
||||||
|
|
||||||
|
/** |
||||||
|
* Initialize method. |
||||||
|
*/ |
||||||
|
init { |
||||||
|
setRenderer(mRenderer) |
||||||
|
renderMode = RENDERMODE_WHEN_DIRTY |
||||||
|
setOnTouchListener(this) |
||||||
|
|
||||||
|
// Even though left and right pages are static we have to allocate room |
||||||
|
// for curl on them too as we are switching meshes. Another way would be |
||||||
|
// to swap texture ids only. |
||||||
|
mPageLeft = CurlMesh(10) |
||||||
|
mPageRight = CurlMesh(10) |
||||||
|
mPageCurl = CurlMesh(10) |
||||||
|
mPageLeft.setFlipTexture(true) |
||||||
|
mPageRight.setFlipTexture(false) |
||||||
|
} |
||||||
|
|
||||||
|
override fun onDrawFrame() { |
||||||
|
// We are not animating. |
||||||
|
if (!mAnimate) { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
val currentTime = System.currentTimeMillis() |
||||||
|
// If animation is done. |
||||||
|
if (currentTime >= mAnimationStartTime + mAnimationDurationTime) { |
||||||
|
if (mAnimationTargetEvent == SET_CURL_TO_RIGHT) { |
||||||
|
// Switch curled page to right. |
||||||
|
val right = mPageCurl |
||||||
|
val curl = mPageRight |
||||||
|
right.setRect(mRenderer.getPageRect(CurlRenderer.PAGE_RIGHT)!!) |
||||||
|
right.setFlipTexture(false) |
||||||
|
right.reset() |
||||||
|
mRenderer.removeCurlMesh(curl) |
||||||
|
mPageCurl = curl |
||||||
|
mPageRight = right |
||||||
|
// If we were curling left page update current index. |
||||||
|
if (mCurlState == CURL_LEFT) { |
||||||
|
--mCurrentIndex |
||||||
|
} |
||||||
|
} else if (mAnimationTargetEvent == SET_CURL_TO_LEFT) { |
||||||
|
// Switch curled page to left. |
||||||
|
val left = mPageCurl |
||||||
|
val curl = mPageLeft |
||||||
|
left.setRect(mRenderer.getPageRect(CurlRenderer.PAGE_LEFT)!!) |
||||||
|
left.setFlipTexture(true) |
||||||
|
left.reset() |
||||||
|
mRenderer.removeCurlMesh(curl) |
||||||
|
if (!mRenderLeftPage) { |
||||||
|
mRenderer.removeCurlMesh(left) |
||||||
|
} |
||||||
|
mPageCurl = curl |
||||||
|
mPageLeft = left |
||||||
|
// If we were curling right page update current index. |
||||||
|
if (mCurlState == CURL_RIGHT) { |
||||||
|
++mCurrentIndex |
||||||
|
} |
||||||
|
} |
||||||
|
mCurlState = CURL_NONE |
||||||
|
mAnimate = false |
||||||
|
requestRender() |
||||||
|
} else { |
||||||
|
mPointerPos.mPos.set(mAnimationSource) |
||||||
|
var t = 1f - (currentTime - mAnimationStartTime).toFloat() / mAnimationDurationTime |
||||||
|
t = 1f - t * t * t * (3 - 2 * t) |
||||||
|
mPointerPos.mPos.x += (mAnimationTarget.x - mAnimationSource.x) * t |
||||||
|
mPointerPos.mPos.y += (mAnimationTarget.y - mAnimationSource.y) * t |
||||||
|
updateCurlPos(mPointerPos) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun onPageSizeChanged(width: Int, height: Int) { |
||||||
|
mPageBitmapWidth = width |
||||||
|
mPageBitmapHeight = height |
||||||
|
updatePages() |
||||||
|
requestRender() |
||||||
|
} |
||||||
|
|
||||||
|
public override fun onSizeChanged(w: Int, h: Int, ow: Int, oh: Int) { |
||||||
|
super.onSizeChanged(w, h, ow, oh) |
||||||
|
requestRender() |
||||||
|
if (mSizeChangedObserver != null) { |
||||||
|
mSizeChangedObserver!!.onSizeChanged(w, h) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun onSurfaceCreated() { |
||||||
|
// In case surface is recreated, let page meshes drop allocated texture |
||||||
|
// ids and ask for new ones. There's no need to set textures here as |
||||||
|
// onPageSizeChanged should be called later on. |
||||||
|
mPageLeft.resetTexture() |
||||||
|
mPageRight.resetTexture() |
||||||
|
mPageCurl.resetTexture() |
||||||
|
} |
||||||
|
|
||||||
|
override fun onTouch(view: View, me: MotionEvent): Boolean { |
||||||
|
// No dragging during animation at the moment. |
||||||
|
if (mAnimate || mPageProvider == null) { |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
// We need page rects quite extensively so get them for later use. |
||||||
|
val rightRect = mRenderer.getPageRect(CurlRenderer.PAGE_RIGHT) |
||||||
|
val leftRect = mRenderer.getPageRect(CurlRenderer.PAGE_LEFT) |
||||||
|
|
||||||
|
// Store pointer position. |
||||||
|
mPointerPos.mPos.set(me.x, me.y) |
||||||
|
mRenderer.translate(mPointerPos.mPos) |
||||||
|
if (mEnableTouchPressure) { |
||||||
|
mPointerPos.mPressure = me.pressure |
||||||
|
} else { |
||||||
|
mPointerPos.mPressure = 0.8f |
||||||
|
} |
||||||
|
|
||||||
|
when (me.action) { |
||||||
|
MotionEvent.ACTION_DOWN -> { |
||||||
|
run { |
||||||
|
|
||||||
|
// Once we receive pointer down event its position is mapped to |
||||||
|
// right or left edge of page and that'll be the position from where |
||||||
|
// user is holding the paper to make curl happen. |
||||||
|
mDragStartPos.set(mPointerPos.mPos) |
||||||
|
|
||||||
|
// First we make sure it's not over or below page. Pages are |
||||||
|
// supposed to be same height so it really doesn't matter do we use |
||||||
|
// left or right one. |
||||||
|
if (mDragStartPos.y > rightRect!!.top) { |
||||||
|
mDragStartPos.y = rightRect.top |
||||||
|
} else if (mDragStartPos.y < rightRect.bottom) { |
||||||
|
mDragStartPos.y = rightRect.bottom |
||||||
|
} |
||||||
|
|
||||||
|
// Then we have to make decisions for the user whether curl is going |
||||||
|
// to happen from left or right, and on which page. |
||||||
|
if (mViewMode == SHOW_TWO_PAGES) { |
||||||
|
// If we have an open book and pointer is on the left from right |
||||||
|
// page we'll mark drag position to left edge of left page. |
||||||
|
// Additionally checking mCurrentIndex is higher than zero tells |
||||||
|
// us there is a visible page at all. |
||||||
|
if (mDragStartPos.x < rightRect.left && mCurrentIndex > 0) { |
||||||
|
mDragStartPos.x = leftRect!!.left |
||||||
|
startCurl(CURL_LEFT) |
||||||
|
} else if (mDragStartPos.x >= rightRect.left && mCurrentIndex < mPageProvider!!.pageCount) { |
||||||
|
mDragStartPos.x = rightRect.right |
||||||
|
if (!mAllowLastPageCurl && mCurrentIndex >= mPageProvider!!.pageCount - 1) { |
||||||
|
return false |
||||||
|
} |
||||||
|
startCurl(CURL_RIGHT) |
||||||
|
}// Otherwise check pointer is on right page's side. |
||||||
|
} else if (mViewMode == SHOW_ONE_PAGE) { |
||||||
|
val halfX = (rightRect.right + rightRect.left) / 2 |
||||||
|
if (mDragStartPos.x < halfX && mCurrentIndex > 0) { |
||||||
|
mDragStartPos.x = rightRect.left |
||||||
|
startCurl(CURL_LEFT) |
||||||
|
} else if (mDragStartPos.x >= halfX && mCurrentIndex < mPageProvider!!.pageCount) { |
||||||
|
mDragStartPos.x = rightRect.right |
||||||
|
if (!mAllowLastPageCurl && mCurrentIndex >= mPageProvider!!.pageCount - 1) { |
||||||
|
return false |
||||||
|
} |
||||||
|
startCurl(CURL_RIGHT) |
||||||
|
} |
||||||
|
} |
||||||
|
// If we have are in curl state, let this case clause flow through |
||||||
|
// to next one. We have pointer position and drag position defined |
||||||
|
// and this will create first render request given these points. |
||||||
|
if (mCurlState == CURL_NONE) { |
||||||
|
return false |
||||||
|
} |
||||||
|
} |
||||||
|
updateCurlPos(mPointerPos) |
||||||
|
} |
||||||
|
MotionEvent.ACTION_MOVE -> { |
||||||
|
updateCurlPos(mPointerPos) |
||||||
|
} |
||||||
|
MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> { |
||||||
|
if (mCurlState == CURL_LEFT || mCurlState == CURL_RIGHT) { |
||||||
|
// Animation source is the point from where animation starts. |
||||||
|
// Also it's handled in a way we actually simulate touch events |
||||||
|
// meaning the output is exactly the same as if user drags the |
||||||
|
// page to other side. While not producing the best looking |
||||||
|
// result (which is easier done by altering curl position and/or |
||||||
|
// direction directly), this is done in a hope it made code a |
||||||
|
// bit more readable and easier to maintain. |
||||||
|
mAnimationSource.set(mPointerPos.mPos) |
||||||
|
mAnimationStartTime = System.currentTimeMillis() |
||||||
|
|
||||||
|
// Given the explanation, here we decide whether to simulate |
||||||
|
// drag to left or right end. |
||||||
|
if (mViewMode == SHOW_ONE_PAGE && mPointerPos.mPos.x > (rightRect!!.left + rightRect.right) / 2 || mViewMode == SHOW_TWO_PAGES && mPointerPos.mPos.x > rightRect!!.left) { |
||||||
|
// On right side target is always right page's right border. |
||||||
|
mAnimationTarget.set(mDragStartPos) |
||||||
|
mAnimationTarget.x = mRenderer |
||||||
|
.getPageRect(CurlRenderer.PAGE_RIGHT)!!.right |
||||||
|
mAnimationTargetEvent = SET_CURL_TO_RIGHT |
||||||
|
} else { |
||||||
|
// On left side target depends on visible pages. |
||||||
|
mAnimationTarget.set(mDragStartPos) |
||||||
|
if (mCurlState == CURL_RIGHT || mViewMode == SHOW_TWO_PAGES) { |
||||||
|
mAnimationTarget.x = leftRect!!.left |
||||||
|
} else { |
||||||
|
mAnimationTarget.x = rightRect!!.left |
||||||
|
} |
||||||
|
mAnimationTargetEvent = SET_CURL_TO_LEFT |
||||||
|
} |
||||||
|
mAnimate = true |
||||||
|
requestRender() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Allow the last page to curl. |
||||||
|
*/ |
||||||
|
fun setAllowLastPageCurl(allowLastPageCurl: Boolean) { |
||||||
|
mAllowLastPageCurl = allowLastPageCurl |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets mPageCurl curl position. |
||||||
|
*/ |
||||||
|
private fun setCurlPos(curlPos: PointF, curlDir: PointF, radius: Double) { |
||||||
|
|
||||||
|
// First reposition curl so that page doesn't 'rip off' from book. |
||||||
|
if (mCurlState == CURL_RIGHT || mCurlState == CURL_LEFT && mViewMode == SHOW_ONE_PAGE) { |
||||||
|
val pageRect = mRenderer.getPageRect(CurlRenderer.PAGE_RIGHT) |
||||||
|
if (curlPos.x >= pageRect!!.right) { |
||||||
|
mPageCurl.reset() |
||||||
|
requestRender() |
||||||
|
return |
||||||
|
} |
||||||
|
if (curlPos.x < pageRect.left) { |
||||||
|
curlPos.x = pageRect.left |
||||||
|
} |
||||||
|
if (curlDir.y != 0f) { |
||||||
|
val diffX = curlPos.x - pageRect.left |
||||||
|
val leftY = curlPos.y + diffX * curlDir.x / curlDir.y |
||||||
|
if (curlDir.y < 0 && leftY < pageRect.top) { |
||||||
|
curlDir.x = curlPos.y - pageRect.top |
||||||
|
curlDir.y = pageRect.left - curlPos.x |
||||||
|
} else if (curlDir.y > 0 && leftY > pageRect.bottom) { |
||||||
|
curlDir.x = pageRect.bottom - curlPos.y |
||||||
|
curlDir.y = curlPos.x - pageRect.left |
||||||
|
} |
||||||
|
} |
||||||
|
} else if (mCurlState == CURL_LEFT) { |
||||||
|
val pageRect = mRenderer.getPageRect(CurlRenderer.PAGE_LEFT) |
||||||
|
if (curlPos.x <= pageRect!!.left) { |
||||||
|
mPageCurl.reset() |
||||||
|
requestRender() |
||||||
|
return |
||||||
|
} |
||||||
|
if (curlPos.x > pageRect.right) { |
||||||
|
curlPos.x = pageRect.right |
||||||
|
} |
||||||
|
if (curlDir.y != 0f) { |
||||||
|
val diffX = curlPos.x - pageRect.right |
||||||
|
val rightY = curlPos.y + diffX * curlDir.x / curlDir.y |
||||||
|
if (curlDir.y < 0 && rightY < pageRect.top) { |
||||||
|
curlDir.x = pageRect.top - curlPos.y |
||||||
|
curlDir.y = curlPos.x - pageRect.right |
||||||
|
} else if (curlDir.y > 0 && rightY > pageRect.bottom) { |
||||||
|
curlDir.x = curlPos.y - pageRect.bottom |
||||||
|
curlDir.y = pageRect.right - curlPos.x |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Finally normalize direction vector and do rendering. |
||||||
|
val dist = sqrt((curlDir.x * curlDir.x + curlDir.y * curlDir.y).toDouble()) |
||||||
|
if (dist != 0.0) { |
||||||
|
curlDir.x /= dist.toFloat() |
||||||
|
curlDir.y /= dist.toFloat() |
||||||
|
mPageCurl.curl(curlPos, curlDir, radius) |
||||||
|
} else { |
||||||
|
mPageCurl.reset() |
||||||
|
} |
||||||
|
|
||||||
|
requestRender() |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* If set to true, touch event pressure information is used to adjust curl |
||||||
|
* radius. The more you press, the flatter the curl becomes. This is |
||||||
|
* somewhat experimental and results may vary significantly between devices. |
||||||
|
* On emulator pressure information seems to be flat 1.0f which is maximum |
||||||
|
* value and therefore not very much of use. |
||||||
|
*/ |
||||||
|
fun setEnableTouchPressure(enableTouchPressure: Boolean) { |
||||||
|
mEnableTouchPressure = enableTouchPressure |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Set margins (or padding). Note: margins are proportional. Meaning a value |
||||||
|
* of .1f will produce a 10% margin. |
||||||
|
*/ |
||||||
|
fun setMargins(left: Float, top: Float, right: Float, bottom: Float) { |
||||||
|
mRenderer.setMargins(left, top, right, bottom) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Update/set page provider. |
||||||
|
*/ |
||||||
|
fun setPageProvider(pageProvider: PageProvider) { |
||||||
|
mPageProvider = pageProvider |
||||||
|
mCurrentIndex = 0 |
||||||
|
updatePages() |
||||||
|
requestRender() |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Setter for whether left side page is rendered. This is useful mostly for |
||||||
|
* situations where right (main) page is aligned to left side of screen and |
||||||
|
* left page is not visible anyway. |
||||||
|
*/ |
||||||
|
fun setRenderLeftPage(renderLeftPage: Boolean) { |
||||||
|
mRenderLeftPage = renderLeftPage |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets SizeChangedObserver for this View. Call back method is called from |
||||||
|
* this View's onSizeChanged method. |
||||||
|
*/ |
||||||
|
fun setSizeChangedObserver(observer: SizeChangedObserver) { |
||||||
|
mSizeChangedObserver = observer |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets view mode. Value can be either SHOW_ONE_PAGE or SHOW_TWO_PAGES. In |
||||||
|
* former case right page is made size of display, and in latter case two |
||||||
|
* pages are laid on visible area. |
||||||
|
*/ |
||||||
|
fun setViewMode(viewMode: Int) { |
||||||
|
when (viewMode) { |
||||||
|
SHOW_ONE_PAGE -> { |
||||||
|
mViewMode = viewMode |
||||||
|
mPageLeft.setFlipTexture(true) |
||||||
|
mRenderer.setViewMode(CurlRenderer.SHOW_ONE_PAGE) |
||||||
|
} |
||||||
|
SHOW_TWO_PAGES -> { |
||||||
|
mViewMode = viewMode |
||||||
|
mPageLeft.setFlipTexture(false) |
||||||
|
mRenderer.setViewMode(CurlRenderer.SHOW_TWO_PAGES) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Switches meshes and loads new bitmaps if available. Updated to support 2 |
||||||
|
* pages in landscape |
||||||
|
*/ |
||||||
|
private fun startCurl(page: Int) { |
||||||
|
when (page) { |
||||||
|
|
||||||
|
// Once right side page is curled, first right page is assigned into |
||||||
|
// curled page. And if there are more bitmaps available new bitmap is |
||||||
|
// loaded into right side mesh. |
||||||
|
CURL_RIGHT -> { |
||||||
|
// Remove meshes from renderer. |
||||||
|
mRenderer.removeCurlMesh(mPageLeft) |
||||||
|
mRenderer.removeCurlMesh(mPageRight) |
||||||
|
mRenderer.removeCurlMesh(mPageCurl) |
||||||
|
|
||||||
|
// We are curling right page. |
||||||
|
val curl = mPageRight |
||||||
|
mPageRight = mPageCurl |
||||||
|
mPageCurl = curl |
||||||
|
|
||||||
|
if (mCurrentIndex > 0) { |
||||||
|
mPageLeft.setFlipTexture(true) |
||||||
|
mPageLeft.setRect(mRenderer.getPageRect(CurlRenderer.PAGE_LEFT)!!) |
||||||
|
mPageLeft.reset() |
||||||
|
if (mRenderLeftPage) { |
||||||
|
mRenderer.addCurlMesh(mPageLeft) |
||||||
|
} |
||||||
|
} |
||||||
|
if (mCurrentIndex < mPageProvider!!.pageCount - 1) { |
||||||
|
updatePage(mPageRight.texturePage, mCurrentIndex + 1) |
||||||
|
mPageRight.setRect( |
||||||
|
mRenderer.getPageRect(CurlRenderer.PAGE_RIGHT)!! |
||||||
|
) |
||||||
|
mPageRight.setFlipTexture(false) |
||||||
|
mPageRight.reset() |
||||||
|
mRenderer.addCurlMesh(mPageRight) |
||||||
|
} |
||||||
|
|
||||||
|
// Add curled page to renderer. |
||||||
|
mPageCurl.setRect(mRenderer.getPageRect(CurlRenderer.PAGE_RIGHT)!!) |
||||||
|
mPageCurl.setFlipTexture(false) |
||||||
|
mPageCurl.reset() |
||||||
|
mRenderer.addCurlMesh(mPageCurl) |
||||||
|
|
||||||
|
mCurlState = CURL_RIGHT |
||||||
|
} |
||||||
|
|
||||||
|
// On left side curl, left page is assigned to curled page. And if |
||||||
|
// there are more bitmaps available before currentIndex, new bitmap |
||||||
|
// is loaded into left page. |
||||||
|
CURL_LEFT -> { |
||||||
|
// Remove meshes from renderer. |
||||||
|
mRenderer.removeCurlMesh(mPageLeft) |
||||||
|
mRenderer.removeCurlMesh(mPageRight) |
||||||
|
mRenderer.removeCurlMesh(mPageCurl) |
||||||
|
|
||||||
|
// We are curling left page. |
||||||
|
val curl = mPageLeft |
||||||
|
mPageLeft = mPageCurl |
||||||
|
mPageCurl = curl |
||||||
|
|
||||||
|
if (mCurrentIndex > 1) { |
||||||
|
updatePage(mPageLeft.texturePage, mCurrentIndex - 2) |
||||||
|
mPageLeft.setFlipTexture(true) |
||||||
|
mPageLeft |
||||||
|
.setRect(mRenderer.getPageRect(CurlRenderer.PAGE_LEFT)!!) |
||||||
|
mPageLeft.reset() |
||||||
|
if (mRenderLeftPage) { |
||||||
|
mRenderer.addCurlMesh(mPageLeft) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// If there is something to show on right page add it to renderer. |
||||||
|
if (mCurrentIndex < mPageProvider!!.pageCount) { |
||||||
|
mPageRight.setFlipTexture(false) |
||||||
|
mPageRight.setRect( |
||||||
|
mRenderer.getPageRect(CurlRenderer.PAGE_RIGHT)!! |
||||||
|
) |
||||||
|
mPageRight.reset() |
||||||
|
mRenderer.addCurlMesh(mPageRight) |
||||||
|
} |
||||||
|
|
||||||
|
// How dragging previous page happens depends on view mode. |
||||||
|
if (mViewMode == SHOW_ONE_PAGE || mCurlState == CURL_LEFT && mViewMode == SHOW_TWO_PAGES) { |
||||||
|
mPageCurl.setRect( |
||||||
|
mRenderer.getPageRect(CurlRenderer.PAGE_RIGHT)!! |
||||||
|
) |
||||||
|
mPageCurl.setFlipTexture(false) |
||||||
|
} else { |
||||||
|
mPageCurl.setRect(mRenderer.getPageRect(CurlRenderer.PAGE_LEFT)!!) |
||||||
|
mPageCurl.setFlipTexture(true) |
||||||
|
} |
||||||
|
mPageCurl.reset() |
||||||
|
mRenderer.addCurlMesh(mPageCurl) |
||||||
|
|
||||||
|
mCurlState = CURL_LEFT |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Updates curl position. |
||||||
|
*/ |
||||||
|
private fun updateCurlPos(pointerPos: PointerPosition) { |
||||||
|
|
||||||
|
// Default curl radius. |
||||||
|
var radius = (mRenderer.getPageRect(CURL_RIGHT)!!.width() / 3).toDouble() |
||||||
|
// TODO: This is not an optimal solution. Based on feedback received so |
||||||
|
// far; pressure is not very accurate, it may be better not to map |
||||||
|
// coefficient to range [0f, 1f] but something like [.2f, 1f] instead. |
||||||
|
// Leaving it as is until get my hands on a real device. On emulator |
||||||
|
// this doesn't work anyway. |
||||||
|
radius *= max(1f - pointerPos.mPressure, 0f).toDouble() |
||||||
|
// NOTE: Here we set pointerPos to mCurlPos. It might be a bit confusing |
||||||
|
// later to see e.g "mCurlPos.x - mDragStartPos.x" used. But it's |
||||||
|
// actually pointerPos we are doing calculations against. Why? Simply to |
||||||
|
// optimize code a bit with the cost of making it unreadable. Otherwise |
||||||
|
// we had to this in both of the next if-else branches. |
||||||
|
mCurlPos.set(pointerPos.mPos) |
||||||
|
|
||||||
|
// If curl happens on right page, or on left page on two page mode, |
||||||
|
// we'll calculate curl position from pointerPos. |
||||||
|
if (mCurlState == CURL_RIGHT || mCurlState == CURL_LEFT && mViewMode == SHOW_TWO_PAGES) { |
||||||
|
|
||||||
|
mCurlDir.x = mCurlPos.x - mDragStartPos.x |
||||||
|
mCurlDir.y = mCurlPos.y - mDragStartPos.y |
||||||
|
val dist = |
||||||
|
sqrt((mCurlDir.x * mCurlDir.x + mCurlDir.y * mCurlDir.y).toDouble()).toFloat() |
||||||
|
|
||||||
|
// Adjust curl radius so that if page is dragged far enough on |
||||||
|
// opposite side, radius gets closer to zero. |
||||||
|
val pageWidth = mRenderer.getPageRect(CurlRenderer.PAGE_RIGHT)!! |
||||||
|
.width() |
||||||
|
var curlLen = radius * Math.PI |
||||||
|
if (dist > pageWidth * 2 - curlLen) { |
||||||
|
curlLen = max(pageWidth * 2 - dist, 0f).toDouble() |
||||||
|
radius = curlLen / Math.PI |
||||||
|
} |
||||||
|
|
||||||
|
// Actual curl position calculation. |
||||||
|
if (dist >= curlLen) { |
||||||
|
val translate = (dist - curlLen) / 2 |
||||||
|
if (mViewMode == SHOW_TWO_PAGES) { |
||||||
|
mCurlPos.x -= (mCurlDir.x * translate / dist).toFloat() |
||||||
|
} else { |
||||||
|
val pageLeftX = mRenderer |
||||||
|
.getPageRect(CurlRenderer.PAGE_RIGHT)!!.left |
||||||
|
radius = max( |
||||||
|
min((mCurlPos.x - pageLeftX).toDouble(), radius), |
||||||
|
0.0 |
||||||
|
) |
||||||
|
} |
||||||
|
mCurlPos.y -= (mCurlDir.y * translate / dist).toFloat() |
||||||
|
} else { |
||||||
|
val angle = Math.PI * sqrt(dist / curlLen) |
||||||
|
val translate = radius * sin(angle) |
||||||
|
mCurlPos.x += (mCurlDir.x * translate / dist).toFloat() |
||||||
|
mCurlPos.y += (mCurlDir.y * translate / dist).toFloat() |
||||||
|
} |
||||||
|
} else if (mCurlState == CURL_LEFT) { |
||||||
|
|
||||||
|
// Adjust radius regarding how close to page edge we are. |
||||||
|
val pageLeftX = mRenderer.getPageRect(CurlRenderer.PAGE_RIGHT)!!.left |
||||||
|
radius = max(min((mCurlPos.x - pageLeftX).toDouble(), radius), 0.0) |
||||||
|
|
||||||
|
val pageRightX = mRenderer.getPageRect(CurlRenderer.PAGE_RIGHT)!!.right |
||||||
|
mCurlPos.x -= min((pageRightX - mCurlPos.x).toDouble(), radius).toFloat() |
||||||
|
mCurlDir.x = mCurlPos.x + mDragStartPos.x |
||||||
|
mCurlDir.y = mCurlPos.y - mDragStartPos.y |
||||||
|
}// Otherwise we'll let curl follow pointer position. |
||||||
|
|
||||||
|
setCurlPos(mCurlPos, mCurlDir, radius) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Updates given CurlPage via PageProvider for page located at index. |
||||||
|
*/ |
||||||
|
private fun updatePage(page: CurlPage, index: Int) { |
||||||
|
// First reset page to initial state. |
||||||
|
page.reset() |
||||||
|
// Ask page provider to fill it up with bitmaps and colors. |
||||||
|
mPageProvider!!.updatePage( |
||||||
|
page, mPageBitmapWidth, mPageBitmapHeight, |
||||||
|
index |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Updates bitmaps for page meshes. |
||||||
|
*/ |
||||||
|
private fun updatePages() { |
||||||
|
if (mPageProvider == null || mPageBitmapWidth <= 0 |
||||||
|
|| mPageBitmapHeight <= 0 |
||||||
|
) { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// Remove meshes from renderer. |
||||||
|
mRenderer.removeCurlMesh(mPageLeft) |
||||||
|
mRenderer.removeCurlMesh(mPageRight) |
||||||
|
mRenderer.removeCurlMesh(mPageCurl) |
||||||
|
|
||||||
|
var leftIdx = mCurrentIndex - 1 |
||||||
|
var rightIdx = mCurrentIndex |
||||||
|
var curlIdx = -1 |
||||||
|
if (mCurlState == CURL_LEFT) { |
||||||
|
curlIdx = leftIdx |
||||||
|
--leftIdx |
||||||
|
} else if (mCurlState == CURL_RIGHT) { |
||||||
|
curlIdx = rightIdx |
||||||
|
++rightIdx |
||||||
|
} |
||||||
|
|
||||||
|
if (rightIdx >= 0 && rightIdx < mPageProvider!!.pageCount) { |
||||||
|
updatePage(mPageRight.texturePage, rightIdx) |
||||||
|
mPageRight.setFlipTexture(false) |
||||||
|
mPageRight.setRect(mRenderer.getPageRect(CurlRenderer.PAGE_RIGHT)!!) |
||||||
|
mPageRight.reset() |
||||||
|
mRenderer.addCurlMesh(mPageRight) |
||||||
|
} |
||||||
|
if (leftIdx >= 0 && leftIdx < mPageProvider!!.pageCount) { |
||||||
|
updatePage(mPageLeft.texturePage, leftIdx) |
||||||
|
mPageLeft.setFlipTexture(true) |
||||||
|
mPageLeft.setRect(mRenderer.getPageRect(CurlRenderer.PAGE_LEFT)!!) |
||||||
|
mPageLeft.reset() |
||||||
|
if (mRenderLeftPage) { |
||||||
|
mRenderer.addCurlMesh(mPageLeft) |
||||||
|
} |
||||||
|
} |
||||||
|
if (curlIdx >= 0 && curlIdx < mPageProvider!!.pageCount) { |
||||||
|
updatePage(mPageCurl.texturePage, curlIdx) |
||||||
|
|
||||||
|
if (mCurlState == CURL_RIGHT) { |
||||||
|
mPageCurl.setFlipTexture(true) |
||||||
|
mPageCurl.setRect( |
||||||
|
mRenderer.getPageRect(CurlRenderer.PAGE_RIGHT)!! |
||||||
|
) |
||||||
|
} else { |
||||||
|
mPageCurl.setFlipTexture(false) |
||||||
|
mPageCurl |
||||||
|
.setRect(mRenderer.getPageRect(CurlRenderer.PAGE_LEFT)!!) |
||||||
|
} |
||||||
|
|
||||||
|
mPageCurl.reset() |
||||||
|
mRenderer.addCurlMesh(mPageCurl) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Provider for feeding 'book' with bitmaps which are used for rendering |
||||||
|
* pages. |
||||||
|
*/ |
||||||
|
interface PageProvider { |
||||||
|
|
||||||
|
/** |
||||||
|
* Return number of pages available. |
||||||
|
*/ |
||||||
|
val pageCount: Int |
||||||
|
|
||||||
|
/** |
||||||
|
* Called once new bitmaps/textures are needed. Width and height are in |
||||||
|
* pixels telling the size it will be drawn on screen and following them |
||||||
|
* ensures that aspect ratio remains. But it's possible to return bitmap |
||||||
|
* of any size though. You should use provided CurlPage for storing page |
||||||
|
* information for requested page number.<br></br> |
||||||
|
* <br></br> |
||||||
|
* Index is a number between 0 and getBitmapCount() - 1. |
||||||
|
*/ |
||||||
|
fun updatePage(page: CurlPage, width: Int, height: Int, index: Int) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Simple holder for pointer position. |
||||||
|
*/ |
||||||
|
private inner class PointerPosition { |
||||||
|
internal var mPos = PointF() |
||||||
|
internal var mPressure: Float = 0.toFloat() |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Observer interface for handling CurlView size changes. |
||||||
|
*/ |
||||||
|
interface SizeChangedObserver { |
||||||
|
|
||||||
|
/** |
||||||
|
* Called once CurlView size changes. |
||||||
|
*/ |
||||||
|
fun onSizeChanged(width: Int, height: Int) |
||||||
|
} |
||||||
|
|
||||||
|
companion object { |
||||||
|
|
||||||
|
// Curl state. We are flipping none, left or right page. |
||||||
|
private const val CURL_LEFT = 1 |
||||||
|
private const val CURL_NONE = 0 |
||||||
|
private const val CURL_RIGHT = 2 |
||||||
|
|
||||||
|
// Constants for mAnimationTargetEvent. |
||||||
|
private const val SET_CURL_TO_LEFT = 1 |
||||||
|
private const val SET_CURL_TO_RIGHT = 2 |
||||||
|
|
||||||
|
// Shows one page at the center of view. |
||||||
|
const val SHOW_ONE_PAGE = 1 |
||||||
|
// Shows two pages side by side. |
||||||
|
const val SHOW_TWO_PAGES = 2 |
||||||
|
} |
||||||
|
|
||||||
|
} |
Loading…
Reference in new issue