diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt index 9d17e0f18..84dafcaa6 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt @@ -9,7 +9,6 @@ import android.widget.FrameLayout import io.legado.app.constant.PreferKey import io.legado.app.help.ReadBookConfig import io.legado.app.service.help.ReadBook -import io.legado.app.ui.book.read.page.curl.CurlView import io.legado.app.ui.book.read.page.delegate.* import io.legado.app.utils.activity import io.legado.app.utils.getPrefInt @@ -26,7 +25,6 @@ class PageView(context: Context, attrs: AttributeSet) : var prevPage: ContentView? = null var curPage: ContentView? = null var nextPage: ContentView? = null - var curlView: CurlView? = null init { callBack = activity as? CallBack @@ -91,10 +89,6 @@ class PageView(context: Context, attrs: AttributeSet) : } fun upPageAnim(pageAnim: Int) { - if (curlView != null) { - removeView(curlView) - curlView = null - } pageDelegate = null pageDelegate = when (pageAnim) { 0 -> CoverPageDelegate(this) diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/curl/CurlMesh.java b/app/src/main/java/io/legado/app/ui/book/read/page/curl/CurlMesh.java deleted file mode 100644 index 65ddbc363..000000000 --- a/app/src/main/java/io/legado/app/ui/book/read/page/curl/CurlMesh.java +++ /dev/null @@ -1,954 +0,0 @@ -package io.legado.app.ui.book.read.page.curl; - -import android.graphics.Bitmap; -import android.graphics.Color; -import android.graphics.PointF; -import android.graphics.RectF; -import android.opengl.GLUtils; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.FloatBuffer; - -import javax.microedition.khronos.opengles.GL10; - -/** - * Class implementing actual curl/page rendering. - * - * @author harism - */ -public class CurlMesh { - - // Flag for rendering some lines used for developing. Shows - // curl position and one for the direction from the - // position given. Comes handy once playing around with different - // ways for following pointer. - private static final boolean DRAW_CURL_POSITION = false; - // Flag for drawing polygon outlines. Using this flag crashes on emulator - // due to reason unknown to me. Leaving it here anyway as seeing polygon - // outlines gives good insight how original rectangle is divided. - private static final boolean DRAW_POLYGON_OUTLINES = false; - // Flag for enabling shadow rendering. - private static final boolean DRAW_SHADOW = true; - // Flag for texture rendering. While this is likely something you - // don't want to do it's been used for development purposes as texture - // rendering is rather slow on emulator. - private static final boolean DRAW_TEXTURE = true; - - // Colors for shadow. Inner one is the color drawn next to surface where - // shadowed area starts and outer one is color shadow ends to. - private static final float[] SHADOW_INNER_COLOR = {0f, 0f, 0f, .5f}; - private static final float[] SHADOW_OUTER_COLOR = {0f, 0f, 0f, .0f}; - - // Let's avoid using 'new' as much as possible. Meaning we introduce arrays - // once here and reuse them on runtime. Doesn't really have very much effect - // but avoids some garbage collections from happening. - private Array mArrDropShadowVertices; - private Array mArrIntersections; - private Array mArrOutputVertices; - private Array mArrRotatedVertices; - private Array mArrScanLines; - private Array mArrSelfShadowVertices; - private Array mArrTempShadowVertices; - private Array mArrTempVertices; - - // Buffers for feeding rasterizer. - private FloatBuffer mBufColors; - private FloatBuffer mBufCurlPositionLines; - private FloatBuffer mBufShadowColors; - private FloatBuffer mBufShadowVertices; - private FloatBuffer mBufTexCoords; - private FloatBuffer mBufVertices; - - private int mCurlPositionLinesCount; - private int mDropShadowCount; - - // Boolean for 'flipping' texture sideways. - private boolean mFlipTexture = false; - // Maximum number of split lines used for creating a curl. - private int mMaxCurlSplits; - - // Bounding rectangle for this mesh. mRectagle[0] = top-left corner, - // mRectangle[1] = bottom-left, mRectangle[2] = top-right and mRectangle[3] - // bottom-right. - private final Vertex[] mRectangle = new Vertex[4]; - private int mSelfShadowCount; - - private boolean mTextureBack = false; - // Texture ids and other variables. - private int[] mTextureIds = null; - private final CurlPage mTexturePage = new CurlPage(); - private final RectF mTextureRectBack = new RectF(); - private final RectF mTextureRectFront = new RectF(); - - private int mVerticesCountBack; - private int mVerticesCountFront; - - /** - * Constructor for mesh object. - * - * @param maxCurlSplits Maximum number curl can be divided into. The bigger the value - * the smoother curl will be. With the cost of having more - * polygons for drawing. - */ - public CurlMesh(int maxCurlSplits) { - // There really is no use for 0 splits. - mMaxCurlSplits = maxCurlSplits < 1 ? 1 : maxCurlSplits; - - mArrScanLines = new Array<>(maxCurlSplits + 2); - mArrOutputVertices = new Array<>(7); - mArrRotatedVertices = new Array<>(4); - mArrIntersections = new Array<>(2); - mArrTempVertices = new Array<>(7 + 4); - for (int i = 0; i < 7 + 4; ++i) { - mArrTempVertices.add(new Vertex()); - } - - if (DRAW_SHADOW) { - mArrSelfShadowVertices = new Array<>( - (mMaxCurlSplits + 2) * 2); - mArrDropShadowVertices = new Array<>( - (mMaxCurlSplits + 2) * 2); - mArrTempShadowVertices = new Array<>( - (mMaxCurlSplits + 2) * 2); - for (int i = 0; i < (mMaxCurlSplits + 2) * 2; ++i) { - mArrTempShadowVertices.add(new ShadowVertex()); - } - } - - // Rectangle consists of 4 vertices. Index 0 = top-left, index 1 = - // bottom-left, index 2 = top-right and index 3 = bottom-right. - for (int i = 0; i < 4; ++i) { - mRectangle[i] = new Vertex(); - } - // Set up shadow penumbra direction to each vertex. We do fake 'self - // shadow' calculations based on this information. - mRectangle[0].mPenumbraX = mRectangle[1].mPenumbraX = mRectangle[1].mPenumbraY = mRectangle[3].mPenumbraY = -1; - mRectangle[0].mPenumbraY = mRectangle[2].mPenumbraX = mRectangle[2].mPenumbraY = mRectangle[3].mPenumbraX = 1; - - if (DRAW_CURL_POSITION) { - mCurlPositionLinesCount = 3; - ByteBuffer hvbb = ByteBuffer - .allocateDirect(mCurlPositionLinesCount * 2 * 2 * 4); - hvbb.order(ByteOrder.nativeOrder()); - mBufCurlPositionLines = hvbb.asFloatBuffer(); - mBufCurlPositionLines.position(0); - } - - // There are 4 vertices from bounding rect, max 2 from adding split line - // to two corners and curl consists of max mMaxCurlSplits lines each - // outputting 2 vertices. - int maxVerticesCount = 4 + 2 + (2 * mMaxCurlSplits); - ByteBuffer vbb = ByteBuffer.allocateDirect(maxVerticesCount * 3 * 4); - vbb.order(ByteOrder.nativeOrder()); - mBufVertices = vbb.asFloatBuffer(); - mBufVertices.position(0); - - if (DRAW_TEXTURE) { - ByteBuffer tbb = ByteBuffer - .allocateDirect(maxVerticesCount * 2 * 4); - tbb.order(ByteOrder.nativeOrder()); - mBufTexCoords = tbb.asFloatBuffer(); - mBufTexCoords.position(0); - } - - ByteBuffer cbb = ByteBuffer.allocateDirect(maxVerticesCount * 4 * 4); - cbb.order(ByteOrder.nativeOrder()); - mBufColors = cbb.asFloatBuffer(); - mBufColors.position(0); - - if (DRAW_SHADOW) { - int maxShadowVerticesCount = (mMaxCurlSplits + 2) * 2 * 2; - ByteBuffer scbb = ByteBuffer - .allocateDirect(maxShadowVerticesCount * 4 * 4); - scbb.order(ByteOrder.nativeOrder()); - mBufShadowColors = scbb.asFloatBuffer(); - mBufShadowColors.position(0); - - ByteBuffer sibb = ByteBuffer - .allocateDirect(maxShadowVerticesCount * 3 * 4); - sibb.order(ByteOrder.nativeOrder()); - mBufShadowVertices = sibb.asFloatBuffer(); - mBufShadowVertices.position(0); - - mDropShadowCount = mSelfShadowCount = 0; - } - } - - /** - * Adds vertex to buffers. - */ - private void addVertex(Vertex vertex) { - mBufVertices.put((float) vertex.mPosX); - mBufVertices.put((float) vertex.mPosY); - mBufVertices.put((float) vertex.mPosZ); - mBufColors.put(vertex.mColorFactor * Color.red(vertex.mColor) / 255f); - mBufColors.put(vertex.mColorFactor * Color.green(vertex.mColor) / 255f); - mBufColors.put(vertex.mColorFactor * Color.blue(vertex.mColor) / 255f); - mBufColors.put(Color.alpha(vertex.mColor) / 255f); - if (DRAW_TEXTURE) { - mBufTexCoords.put((float) vertex.mTexX); - mBufTexCoords.put((float) vertex.mTexY); - } - } - - /** - * Sets curl for this mesh. - * - * @param curlPos Position for curl 'center'. Can be any point on line collinear - * to curl. - * @param curlDir Curl direction, should be normalized. - * @param radius Radius of curl. - */ - public synchronized void curl(PointF curlPos, PointF curlDir, double radius) { - - // First add some 'helper' lines used for development. - if (DRAW_CURL_POSITION) { - mBufCurlPositionLines.position(0); - - mBufCurlPositionLines.put(curlPos.x); - mBufCurlPositionLines.put(curlPos.y - 1.0f); - mBufCurlPositionLines.put(curlPos.x); - mBufCurlPositionLines.put(curlPos.y + 1.0f); - mBufCurlPositionLines.put(curlPos.x - 1.0f); - mBufCurlPositionLines.put(curlPos.y); - mBufCurlPositionLines.put(curlPos.x + 1.0f); - mBufCurlPositionLines.put(curlPos.y); - - mBufCurlPositionLines.put(curlPos.x); - mBufCurlPositionLines.put(curlPos.y); - mBufCurlPositionLines.put(curlPos.x + curlDir.x * 2); - mBufCurlPositionLines.put(curlPos.y + curlDir.y * 2); - - mBufCurlPositionLines.position(0); - } - - // Actual 'curl' implementation starts here. - mBufVertices.position(0); - mBufColors.position(0); - if (DRAW_TEXTURE) { - mBufTexCoords.position(0); - } - - // Calculate curl angle from direction. - double curlAngle = Math.acos(curlDir.x); - curlAngle = curlDir.y > 0 ? -curlAngle : curlAngle; - - // Initiate rotated rectangle which's is translated to curlPos and - // rotated so that curl direction heads to right (1,0). Vertices are - // ordered in ascending order based on x -coordinate at the same time. - // And using y -coordinate in very rare case in which two vertices have - // same x -coordinate. - mArrTempVertices.addAll(mArrRotatedVertices); - mArrRotatedVertices.clear(); - for (int i = 0; i < 4; ++i) { - Vertex v = mArrTempVertices.remove(0); - v.set(mRectangle[i]); - v.translate(-curlPos.x, -curlPos.y); - v.rotateZ(-curlAngle); - int j = 0; - for (; j < mArrRotatedVertices.size(); ++j) { - Vertex v2 = mArrRotatedVertices.get(j); - if (v.mPosX > v2.mPosX) { - break; - } - if (v.mPosX == v2.mPosX && v.mPosY > v2.mPosY) { - break; - } - } - mArrRotatedVertices.add(j, v); - } - - // Rotated rectangle lines/vertex indices. We need to find bounding - // lines for rotated rectangle. After sorting vertices according to - // their x -coordinate we don't have to worry about vertices at indices - // 0 and 1. But due to inaccuracy it's possible vertex 3 is not the - // opposing corner from vertex 0. So we are calculating distance from - // vertex 0 to vertices 2 and 3 - and altering line indices if needed. - // Also vertices/lines are given in an order first one has x -coordinate - // at least the latter one. This property is used in getIntersections to - // see if there is an intersection. - int[][] lines = {{0, 1}, {0, 2}, {1, 3}, {2, 3}}; - { - // TODO: There really has to be more 'easier' way of doing this - - // not including extensive use of sqrt. - Vertex v0 = mArrRotatedVertices.get(0); - Vertex v2 = mArrRotatedVertices.get(2); - Vertex v3 = mArrRotatedVertices.get(3); - double dist2 = Math.sqrt((v0.mPosX - v2.mPosX) - * (v0.mPosX - v2.mPosX) + (v0.mPosY - v2.mPosY) - * (v0.mPosY - v2.mPosY)); - double dist3 = Math.sqrt((v0.mPosX - v3.mPosX) - * (v0.mPosX - v3.mPosX) + (v0.mPosY - v3.mPosY) - * (v0.mPosY - v3.mPosY)); - if (dist2 > dist3) { - lines[1][1] = 3; - lines[2][1] = 2; - } - } - - mVerticesCountFront = mVerticesCountBack = 0; - - if (DRAW_SHADOW) { - mArrTempShadowVertices.addAll(mArrDropShadowVertices); - mArrTempShadowVertices.addAll(mArrSelfShadowVertices); - mArrDropShadowVertices.clear(); - mArrSelfShadowVertices.clear(); - } - - // Length of 'curl' curve. - double curlLength = Math.PI * radius; - // Calculate scan lines. - // TODO: Revisit this code one day. There is room for optimization here. - mArrScanLines.clear(); - if (mMaxCurlSplits > 0) { - mArrScanLines.add((double) 0); - } - for (int i = 1; i < mMaxCurlSplits; ++i) { - mArrScanLines.add((-curlLength * i) / (mMaxCurlSplits - 1)); - } - // As mRotatedVertices is ordered regarding x -coordinate, adding - // this scan line produces scan area picking up vertices which are - // rotated completely. One could say 'until infinity'. - mArrScanLines.add(mArrRotatedVertices.get(3).mPosX - 1); - - // Start from right most vertex. Pretty much the same as first scan area - // is starting from 'infinity'. - double scanXmax = mArrRotatedVertices.get(0).mPosX + 1; - - for (int i = 0; i < mArrScanLines.size(); ++i) { - // Once we have scanXmin and scanXmax we have a scan area to start - // working with. - double scanXmin = mArrScanLines.get(i); - // First iterate 'original' rectangle vertices within scan area. - for (int j = 0; j < mArrRotatedVertices.size(); ++j) { - Vertex v = mArrRotatedVertices.get(j); - // Test if vertex lies within this scan area. - // TODO: Frankly speaking, can't remember why equality check was - // added to both ends. Guessing it was somehow related to case - // where radius=0f, which, given current implementation, could - // be handled much more effectively anyway. - if (v.mPosX >= scanXmin && v.mPosX <= scanXmax) { - // Pop out a vertex from temp vertices. - Vertex n = mArrTempVertices.remove(0); - n.set(v); - // This is done solely for triangulation reasons. Given a - // rotated rectangle it has max 2 vertices having - // intersection. - Array intersections = getIntersections( - mArrRotatedVertices, lines, n.mPosX); - // In a sense one could say we're adding vertices always in - // two, positioned at the ends of intersecting line. And for - // triangulation to work properly they are added based on y - // -coordinate. And this if-else is doing it for us. - if (intersections.size() == 1 - && intersections.get(0).mPosY > v.mPosY) { - // In case intersecting vertex is higher add it first. - mArrOutputVertices.addAll(intersections); - mArrOutputVertices.add(n); - } else if (intersections.size() <= 1) { - // Otherwise add original vertex first. - mArrOutputVertices.add(n); - mArrOutputVertices.addAll(intersections); - } else { - // There should never be more than 1 intersecting - // vertex. But if it happens as a fallback simply skip - // everything. - mArrTempVertices.add(n); - mArrTempVertices.addAll(intersections); - } - } - } - - // Search for scan line intersections. - Array intersections = getIntersections(mArrRotatedVertices, - lines, scanXmin); - - // We expect to get 0 or 2 vertices. In rare cases there's only one - // but in general given a scan line intersecting rectangle there - // should be 2 intersecting vertices. - if (intersections.size() == 2) { - // There were two intersections, add them based on y - // -coordinate, higher first, lower last. - Vertex v1 = intersections.get(0); - Vertex v2 = intersections.get(1); - if (v1.mPosY < v2.mPosY) { - mArrOutputVertices.add(v2); - mArrOutputVertices.add(v1); - } else { - mArrOutputVertices.addAll(intersections); - } - } else if (intersections.size() != 0) { - // This happens in a case in which there is a original vertex - // exactly at scan line or something went very much wrong if - // there are 3+ vertices. What ever the reason just return the - // vertices to temp vertices for later use. In former case it - // was handled already earlier once iterating through - // mRotatedVertices, in latter case it's better to avoid doing - // anything with them. - mArrTempVertices.addAll(intersections); - } - - // Add vertices found during this iteration to vertex etc buffers. - while (mArrOutputVertices.size() > 0) { - Vertex v = mArrOutputVertices.remove(0); - mArrTempVertices.add(v); - - // Local texture front-facing flag. - boolean textureFront; - - // Untouched vertices. - if (i == 0) { - textureFront = true; - mVerticesCountFront++; - } - // 'Completely' rotated vertices. - else if (i == mArrScanLines.size() - 1 || curlLength == 0) { - v.mPosX = -(curlLength + v.mPosX); - v.mPosZ = 2 * radius; - v.mPenumbraX = -v.mPenumbraX; - - textureFront = false; - mVerticesCountBack++; - } - // Vertex lies within 'curl'. - else { - // Even though it's not obvious from the if-else clause, - // here v.mPosX is between [-curlLength, 0]. And we can do - // calculations around a half cylinder. - double rotY = Math.PI * (v.mPosX / curlLength); - v.mPosX = radius * Math.sin(rotY); - v.mPosZ = radius - (radius * Math.cos(rotY)); - v.mPenumbraX *= Math.cos(rotY); - // Map color multiplier to [.1f, 1f] range. - v.mColorFactor = (float) (.1f + .9f * Math.sqrt(Math - .sin(rotY) + 1)); - - if (v.mPosZ >= radius) { - textureFront = false; - mVerticesCountBack++; - } else { - textureFront = true; - mVerticesCountFront++; - } - } - - // We use local textureFront for flipping backside texture - // locally. Plus additionally if mesh is in flip texture mode, - // we'll make the procedure "backwards". Also, until this point, - // texture coordinates are within [0, 1] range so we'll adjust - // them to final texture coordinates too. - if (textureFront != mFlipTexture) { - v.mTexX *= mTextureRectFront.right; - v.mTexY *= mTextureRectFront.bottom; - v.mColor = mTexturePage.getColor(CurlPage.SIDE_FRONT); - } else { - v.mTexX *= mTextureRectBack.right; - v.mTexY *= mTextureRectBack.bottom; - v.mColor = mTexturePage.getColor(CurlPage.SIDE_BACK); - } - - // Move vertex back to 'world' coordinates. - v.rotateZ(curlAngle); - v.translate(curlPos.x, curlPos.y); - addVertex(v); - - // Drop shadow is cast 'behind' the curl. - if (DRAW_SHADOW && v.mPosZ > 0 && v.mPosZ <= radius) { - ShadowVertex sv = mArrTempShadowVertices.remove(0); - sv.mPosX = v.mPosX; - sv.mPosY = v.mPosY; - sv.mPosZ = v.mPosZ; - sv.mPenumbraX = (v.mPosZ / 2) * -curlDir.x; - sv.mPenumbraY = (v.mPosZ / 2) * -curlDir.y; - sv.mPenumbraColor = v.mPosZ / radius; - int idx = (mArrDropShadowVertices.size() + 1) / 2; - mArrDropShadowVertices.add(idx, sv); - } - // Self shadow is cast partly over mesh. - if (DRAW_SHADOW && v.mPosZ > radius) { - ShadowVertex sv = mArrTempShadowVertices.remove(0); - sv.mPosX = v.mPosX; - sv.mPosY = v.mPosY; - sv.mPosZ = v.mPosZ; - sv.mPenumbraX = ((v.mPosZ - radius) / 3) * v.mPenumbraX; - sv.mPenumbraY = ((v.mPosZ - radius) / 3) * v.mPenumbraY; - sv.mPenumbraColor = (v.mPosZ - radius) / (2 * radius); - int idx = (mArrSelfShadowVertices.size() + 1) / 2; - mArrSelfShadowVertices.add(idx, sv); - } - } - - // Switch scanXmin as scanXmax for next iteration. - scanXmax = scanXmin; - } - - mBufVertices.position(0); - mBufColors.position(0); - if (DRAW_TEXTURE) { - mBufTexCoords.position(0); - } - - // Add shadow Vertices. - if (DRAW_SHADOW) { - mBufShadowColors.position(0); - mBufShadowVertices.position(0); - mDropShadowCount = 0; - - for (int i = 0; i < mArrDropShadowVertices.size(); ++i) { - ShadowVertex sv = mArrDropShadowVertices.get(i); - mBufShadowVertices.put((float) sv.mPosX); - mBufShadowVertices.put((float) sv.mPosY); - mBufShadowVertices.put((float) sv.mPosZ); - mBufShadowVertices.put((float) (sv.mPosX + sv.mPenumbraX)); - mBufShadowVertices.put((float) (sv.mPosY + sv.mPenumbraY)); - mBufShadowVertices.put((float) sv.mPosZ); - for (int j = 0; j < 4; ++j) { - double color = SHADOW_OUTER_COLOR[j] - + (SHADOW_INNER_COLOR[j] - SHADOW_OUTER_COLOR[j]) - * sv.mPenumbraColor; - mBufShadowColors.put((float) color); - } - mBufShadowColors.put(SHADOW_OUTER_COLOR); - mDropShadowCount += 2; - } - mSelfShadowCount = 0; - for (int i = 0; i < mArrSelfShadowVertices.size(); ++i) { - ShadowVertex sv = mArrSelfShadowVertices.get(i); - mBufShadowVertices.put((float) sv.mPosX); - mBufShadowVertices.put((float) sv.mPosY); - mBufShadowVertices.put((float) sv.mPosZ); - mBufShadowVertices.put((float) (sv.mPosX + sv.mPenumbraX)); - mBufShadowVertices.put((float) (sv.mPosY + sv.mPenumbraY)); - mBufShadowVertices.put((float) sv.mPosZ); - for (int j = 0; j < 4; ++j) { - double color = SHADOW_OUTER_COLOR[j] - + (SHADOW_INNER_COLOR[j] - SHADOW_OUTER_COLOR[j]) - * sv.mPenumbraColor; - mBufShadowColors.put((float) color); - } - mBufShadowColors.put(SHADOW_OUTER_COLOR); - mSelfShadowCount += 2; - } - mBufShadowColors.position(0); - mBufShadowVertices.position(0); - } - } - - /** - * Calculates intersections for given scan line. - */ - private Array getIntersections(Array vertices, - int[][] lineIndices, double scanX) { - mArrIntersections.clear(); - // Iterate through rectangle lines each re-presented as a pair of - // vertices. - for (int[] lineIndex : lineIndices) { - Vertex v1 = vertices.get(lineIndex[0]); - Vertex v2 = vertices.get(lineIndex[1]); - // Here we expect that v1.mPosX >= v2.mPosX and wont do intersection - // test the opposite way. - if (v1.mPosX > scanX && v2.mPosX < scanX) { - // There is an intersection, calculate coefficient telling 'how - // far' scanX is from v2. - double c = (scanX - v2.mPosX) / (v1.mPosX - v2.mPosX); - Vertex n = mArrTempVertices.remove(0); - n.set(v2); - n.mPosX = scanX; - n.mPosY += (v1.mPosY - v2.mPosY) * c; - if (DRAW_TEXTURE) { - n.mTexX += (v1.mTexX - v2.mTexX) * c; - n.mTexY += (v1.mTexY - v2.mTexY) * c; - } - if (DRAW_SHADOW) { - n.mPenumbraX += (v1.mPenumbraX - v2.mPenumbraX) * c; - n.mPenumbraY += (v1.mPenumbraY - v2.mPenumbraY) * c; - } - mArrIntersections.add(n); - } - } - return mArrIntersections; - } - - /** - * Getter for textures page for this mesh. - */ - public synchronized CurlPage getTexturePage() { - return mTexturePage; - } - - /** - * Renders our page curl mesh. - */ - public synchronized void onDrawFrame(GL10 gl) { - // First allocate texture if there is not one yet. - if (DRAW_TEXTURE && mTextureIds == null) { - // Generate texture. - mTextureIds = new int[2]; - gl.glGenTextures(2, mTextureIds, 0); - for (int textureId : mTextureIds) { - // Set texture attributes. - gl.glBindTexture(GL10.GL_TEXTURE_2D, textureId); - gl.glTexParameterf(GL10.GL_TEXTURE_2D, - GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST); - gl.glTexParameterf(GL10.GL_TEXTURE_2D, - GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_NEAREST); - gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, - GL10.GL_CLAMP_TO_EDGE); - gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, - GL10.GL_CLAMP_TO_EDGE); - } - } - - if (DRAW_TEXTURE && mTexturePage.getTexturesChanged()) { - gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureIds[0]); - Bitmap texture = mTexturePage.getTexture(mTextureRectFront, - CurlPage.SIDE_FRONT); - GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, texture, 0); - texture.recycle(); - - mTextureBack = mTexturePage.hasBackTexture(); - if (mTextureBack) { - gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureIds[1]); - texture = mTexturePage.getTexture(mTextureRectBack, - CurlPage.SIDE_BACK); - GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, texture, 0); - texture.recycle(); - } else { - mTextureRectBack.set(mTextureRectFront); - } - - mTexturePage.recycle(); - reset(); - } - - // Some 'global' settings. - gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); - - // TODO: Drop shadow drawing is done temporarily here to hide some - // problems with its calculation. - if (DRAW_SHADOW) { - gl.glDisable(GL10.GL_TEXTURE_2D); - gl.glEnable(GL10.GL_BLEND); - gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA); - gl.glEnableClientState(GL10.GL_COLOR_ARRAY); - gl.glColorPointer(4, GL10.GL_FLOAT, 0, mBufShadowColors); - gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mBufShadowVertices); - gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, mDropShadowCount); - gl.glDisableClientState(GL10.GL_COLOR_ARRAY); - gl.glDisable(GL10.GL_BLEND); - } - - if (DRAW_TEXTURE) { - gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY); - gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, mBufTexCoords); - } - gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mBufVertices); - // Enable color array. - gl.glEnableClientState(GL10.GL_COLOR_ARRAY); - gl.glColorPointer(4, GL10.GL_FLOAT, 0, mBufColors); - - // Draw front facing blank vertices. - gl.glDisable(GL10.GL_TEXTURE_2D); - gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, mVerticesCountFront); - - // Draw front facing texture. - if (DRAW_TEXTURE) { - gl.glEnable(GL10.GL_BLEND); - gl.glEnable(GL10.GL_TEXTURE_2D); - - if (!mFlipTexture || !mTextureBack) { - gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureIds[0]); - } else { - gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureIds[1]); - } - - gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA); - gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, mVerticesCountFront); - - gl.glDisable(GL10.GL_BLEND); - gl.glDisable(GL10.GL_TEXTURE_2D); - } - - int backStartIdx = Math.max(0, mVerticesCountFront - 2); - int backCount = mVerticesCountFront + mVerticesCountBack - backStartIdx; - - // Draw back facing blank vertices. - gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, backStartIdx, backCount); - - // Draw back facing texture. - if (DRAW_TEXTURE) { - gl.glEnable(GL10.GL_BLEND); - gl.glEnable(GL10.GL_TEXTURE_2D); - - if (mFlipTexture || !mTextureBack) { - gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureIds[0]); - } else { - gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureIds[1]); - } - - gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA); - gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, backStartIdx, backCount); - - gl.glDisable(GL10.GL_BLEND); - gl.glDisable(GL10.GL_TEXTURE_2D); - } - - // Disable textures and color array. - gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY); - gl.glDisableClientState(GL10.GL_COLOR_ARRAY); - - if (DRAW_POLYGON_OUTLINES) { - gl.glEnable(GL10.GL_BLEND); - gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA); - gl.glLineWidth(1.0f); - gl.glColor4f(0.5f, 0.5f, 1.0f, 1.0f); - gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mBufVertices); - gl.glDrawArrays(GL10.GL_LINE_STRIP, 0, mVerticesCountFront); - gl.glDisable(GL10.GL_BLEND); - } - - if (DRAW_CURL_POSITION) { - gl.glEnable(GL10.GL_BLEND); - gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA); - gl.glLineWidth(1.0f); - gl.glColor4f(1.0f, 0.5f, 0.5f, 1.0f); - gl.glVertexPointer(2, GL10.GL_FLOAT, 0, mBufCurlPositionLines); - gl.glDrawArrays(GL10.GL_LINES, 0, mCurlPositionLinesCount * 2); - gl.glDisable(GL10.GL_BLEND); - } - - if (DRAW_SHADOW) { - gl.glEnable(GL10.GL_BLEND); - gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA); - gl.glEnableClientState(GL10.GL_COLOR_ARRAY); - gl.glColorPointer(4, GL10.GL_FLOAT, 0, mBufShadowColors); - gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mBufShadowVertices); - gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, mDropShadowCount, - mSelfShadowCount); - gl.glDisableClientState(GL10.GL_COLOR_ARRAY); - gl.glDisable(GL10.GL_BLEND); - } - - gl.glDisableClientState(GL10.GL_VERTEX_ARRAY); - } - - /** - * Resets mesh to 'initial' state. Meaning this mesh will draw a plain - * textured rectangle after call to this method. - */ - public synchronized void reset() { - mBufVertices.position(0); - mBufColors.position(0); - if (DRAW_TEXTURE) { - mBufTexCoords.position(0); - } - for (int i = 0; i < 4; ++i) { - Vertex tmp = mArrTempVertices.get(0); - tmp.set(mRectangle[i]); - - if (mFlipTexture) { - tmp.mTexX *= mTextureRectBack.right; - tmp.mTexY *= mTextureRectBack.bottom; - tmp.mColor = mTexturePage.getColor(CurlPage.SIDE_BACK); - } else { - tmp.mTexX *= mTextureRectFront.right; - tmp.mTexY *= mTextureRectFront.bottom; - tmp.mColor = mTexturePage.getColor(CurlPage.SIDE_FRONT); - } - - addVertex(tmp); - } - mVerticesCountFront = 4; - mVerticesCountBack = 0; - mBufVertices.position(0); - mBufColors.position(0); - if (DRAW_TEXTURE) { - mBufTexCoords.position(0); - } - - mDropShadowCount = mSelfShadowCount = 0; - } - - /** - * Resets allocated texture id forcing creation of new one. After calling - * this method you most likely want to set bitmap too as it's lost. This - * method should be called only once e.g GL context is re-created as this - * method does not release previous texture id, only makes sure new one is - * requested on next render. - */ - public synchronized void resetTexture() { - mTextureIds = null; - } - - /** - * If true, flips texture sideways. - */ - public synchronized void setFlipTexture(boolean flipTexture) { - mFlipTexture = flipTexture; - if (flipTexture) { - setTexCoords(1f, 0f, 0f, 1f); - } else { - setTexCoords(0f, 0f, 1f, 1f); - } - } - - /** - * Update mesh bounds. - */ - public void setRect(RectF r) { - mRectangle[0].mPosX = r.left; - mRectangle[0].mPosY = r.top; - mRectangle[1].mPosX = r.left; - mRectangle[1].mPosY = r.bottom; - mRectangle[2].mPosX = r.right; - mRectangle[2].mPosY = r.top; - mRectangle[3].mPosX = r.right; - mRectangle[3].mPosY = r.bottom; - } - - /** - * Sets texture coordinates to mRectangle vertices. - */ - private synchronized void setTexCoords(float left, float top, float right, - float bottom) { - mRectangle[0].mTexX = left; - mRectangle[0].mTexY = top; - mRectangle[1].mTexX = left; - mRectangle[1].mTexY = bottom; - mRectangle[2].mTexX = right; - mRectangle[2].mTexY = top; - mRectangle[3].mTexX = right; - mRectangle[3].mTexY = bottom; - } - - /** - * Simple fixed size array implementation. - */ - private class Array { - private Object[] mArray; - private int mCapacity; - private int mSize; - - Array(int capacity) { - mCapacity = capacity; - mArray = new Object[capacity]; - } - - public void add(int index, T item) { - if (index < 0 || index > mSize || mSize >= mCapacity) { - throw new IndexOutOfBoundsException(); - } - System.arraycopy(mArray, index, mArray, index + 1, mSize - index); - mArray[index] = item; - ++mSize; - } - - public void add(T item) { - if (mSize >= mCapacity) { - throw new IndexOutOfBoundsException(); - } - mArray[mSize++] = item; - } - - public void addAll(Array array) { - if (mSize + array.size() > mCapacity) { - throw new IndexOutOfBoundsException(); - } - for (int i = 0; i < array.size(); ++i) { - mArray[mSize++] = array.get(i); - } - } - - public void clear() { - mSize = 0; - } - - @SuppressWarnings("unchecked") - public T get(int index) { - if (index < 0 || index >= mSize) { - throw new IndexOutOfBoundsException(); - } - return (T) mArray[index]; - } - - @SuppressWarnings("unchecked") - public T remove(int index) { - if (index < 0 || index >= mSize) { - throw new IndexOutOfBoundsException(); - } - T item = (T) mArray[index]; - if (mSize - 1 - index >= 0) - System.arraycopy(mArray, index + 1, mArray, index, mSize - 1 - index); - --mSize; - return item; - } - - public int size() { - return mSize; - } - - } - - /** - * Holder for shadow vertex information. - */ - private class ShadowVertex { - double mPenumbraColor; - double mPenumbraX; - double mPenumbraY; - double mPosX; - double mPosY; - double mPosZ; - } - - /** - * Holder for vertex information. - */ - private class Vertex { - int mColor; - float mColorFactor; - double mPenumbraX; - double mPenumbraY; - double mPosX; - double mPosY; - double mPosZ; - double mTexX; - double mTexY; - - Vertex() { - mPosX = mPosY = mPosZ = mTexX = mTexY = 0; - mColorFactor = 1.0f; - } - - void rotateZ(double theta) { - double cos = Math.cos(theta); - double sin = Math.sin(theta); - double x = mPosX * cos + mPosY * sin; - double y = mPosX * -sin + mPosY * cos; - mPosX = x; - mPosY = y; - double px = mPenumbraX * cos + mPenumbraY * sin; - double py = mPenumbraX * -sin + mPenumbraY * cos; - mPenumbraX = px; - mPenumbraY = py; - } - - public void set(Vertex vertex) { - mPosX = vertex.mPosX; - mPosY = vertex.mPosY; - mPosZ = vertex.mPosZ; - mTexX = vertex.mTexX; - mTexY = vertex.mTexY; - mPenumbraX = vertex.mPenumbraX; - mPenumbraY = vertex.mPenumbraY; - mColor = vertex.mColor; - mColorFactor = vertex.mColorFactor; - } - - public void translate(double dx, double dy) { - mPosX += dx; - mPosY += dy; - } - } -} diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/curl/CurlPage.kt b/app/src/main/java/io/legado/app/ui/book/read/page/curl/CurlPage.kt deleted file mode 100644 index 88f2fb16c..000000000 --- a/app/src/main/java/io/legado/app/ui/book/read/page/curl/CurlPage.kt +++ /dev/null @@ -1,191 +0,0 @@ -package io.legado.app.ui.book.read.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 - } - -} diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/curl/CurlRenderer.kt b/app/src/main/java/io/legado/app/ui/book/read/page/curl/CurlRenderer.kt deleted file mode 100644 index 6d44ee1ee..000000000 --- a/app/src/main/java/io/legado/app/ui/book/read/page/curl/CurlRenderer.kt +++ /dev/null @@ -1,221 +0,0 @@ -package io.legado.app.ui.book.read.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(private val mObserver: Observer) : GLSurfaceView.Renderer { - // Background fill color. - private var mBackgroundColor: Int = 0 - // Curl meshes used for static and dynamic rendering. - private val mCurlMeshes: Vector = 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 (!mObserver.canDraw) { - return - } - - 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 - updatePageRect() - - gl.glMatrixMode(GL10.GL_PROJECTION) - gl.glLoadIdentity() - 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) - } - - /** - * 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 - updatePageRect() - } else if (viewMode == SHOW_TWO_PAGES) { - mViewMode = viewMode - updatePageRect() - } - } - - /** - * 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 updatePageRect() { - 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() - - var canDraw: Boolean - } - - 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 - } -} diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/curl/CurlView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/curl/CurlView.kt deleted file mode 100644 index ecd5d410d..000000000 --- a/app/src/main/java/io/legado/app/ui/book/read/page/curl/CurlView.kt +++ /dev/null @@ -1,769 +0,0 @@ -package io.legado.app.ui.book.read.page.curl - -import android.content.Context -import android.graphics.PixelFormat -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 { - var callBack: CallBack? = null - 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 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 - - var mPageProvider: PageProvider? = null - set(value) { - field = value - mCurrentIndex = 0 - updatePages() - requestRender() - } - - /** - * 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 { - setEGLConfigChooser(8, 8, 8, 8, 16, 0) - holder.setFormat(PixelFormat.TRANSLUCENT) - setZOrderOnTop(true) - - 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 var canDraw: Boolean = 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 - callBack?.pageChange(-1) - } - canDraw = false - } 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 - callBack?.pageChange(1) - } - canDraw = false - } - 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 - } - - /** - * 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. - */ - 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.

- *

- * 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) - } - - interface CallBack { - fun pageChange(change: 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 - } - -} diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt index c9e0ca546..3f78253b6 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt @@ -69,7 +69,7 @@ abstract class PageDelegate(protected val pageView: PageView) { var isRunning = false var isStarted = false - protected fun setStartPoint(x: Float, y: Float, invalidate: Boolean = true) { + open fun setStartPoint(x: Float, y: Float, invalidate: Boolean = true) { startX = x startY = y @@ -78,7 +78,7 @@ abstract class PageDelegate(protected val pageView: PageView) { } } - protected fun setTouchPoint(x: Float, y: Float, invalidate: Boolean = true) { + open fun setTouchPoint(x: Float, y: Float, invalidate: Boolean = true) { touchX = x touchY = y diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/SimulationPageDelegate.kt b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/SimulationPageDelegate.kt index eb58e616c..0f2c62475 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/SimulationPageDelegate.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/SimulationPageDelegate.kt @@ -1,126 +1,526 @@ package io.legado.app.ui.book.read.page.delegate -import android.graphics.Canvas -import android.view.MotionEvent +import android.graphics.* +import android.graphics.drawable.GradientDrawable +import android.os.Build import io.legado.app.ui.book.read.page.PageView -import io.legado.app.ui.book.read.page.curl.CurlPage -import io.legado.app.ui.book.read.page.curl.CurlView -import io.legado.app.utils.screenshot -import kotlin.math.abs +import kotlin.math.* -class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView), - CurlView.CallBack { +class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) { - var curlView: CurlView? = null + private var mCornerX = 1 // 拖拽点对应的页脚 + + private var mCornerY = 1 + private val mPath0: Path = Path() + private val mPath1: Path = Path() + // 贝塞尔曲线起始点 + private val mBezierStart1 = PointF() + // 贝塞尔曲线控制点 + private val mBezierControl1 = PointF() + // 贝塞尔曲线顶点 + private val mBezierVertex1 = PointF() + // 贝塞尔曲线结束点 + private var mBezierEnd1 = PointF() + + // 另一条贝塞尔曲线 + private val mBezierStart2 = PointF() + + private val mBezierControl2 = PointF() + private val mBezierVertex2 = PointF() + private var mBezierEnd2 = PointF() + + private var mMiddleX = 0f + private var mMiddleY = 0f + private var mDegrees = 0f + private var mTouchToCornerDis = 0f + private var mColorMatrixFilter: ColorMatrixColorFilter? = null + private val mMatrix: Matrix = Matrix() + private val mMatrixArray = floatArrayOf(0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 1.0f) + + // 是否属于右上左下 + private var mIsRT_LB = false + private var mMaxLength = 0f + // 背面颜色组 + private var mBackShadowColors: IntArray? = null + // 前面颜色组 + private var mFrontShadowColors: IntArray? = null + // 有阴影的GradientDrawable + private var mBackShadowDrawableLR: GradientDrawable? = null + private var mBackShadowDrawableRL: GradientDrawable? = null + private var mFolderShadowDrawableLR: GradientDrawable? = null + private var mFolderShadowDrawableRL: GradientDrawable? = null + + private var mFrontShadowDrawableHBT: GradientDrawable? = null + private var mFrontShadowDrawableHTB: GradientDrawable? = null + private var mFrontShadowDrawableVLR: GradientDrawable? = null + private var mFrontShadowDrawableVRL: GradientDrawable? = null + + private val mPaint: Paint = Paint() init { - pageView.curlView ?: let { - curlView = CurlView(pageView.context) - pageView.curlView = curlView - pageView.addView(curlView) - curlView?.mPageProvider = PageProvider() - curlView?.setSizeChangedObserver(SizeChangedObserver()) - curlView?.callBack = this - } + mMaxLength = hypot(pageView.width.toDouble(), pageView.height.toDouble()).toFloat() + mPaint.style = Paint.Style.FILL + //设置颜色数组 + createDrawable() + val cm = ColorMatrix() + val array = floatArrayOf( + 1f, 0f, 0f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, + 0f, 0f, 1f, 0f, 0f, 0f, 0f, 0f, 1f, 0f + ) + cm.set(array) + mColorMatrixFilter = ColorMatrixColorFilter(cm) } - override fun onTouch(event: MotionEvent): Boolean { - when (event.action) { - MotionEvent.ACTION_DOWN -> { - curlView?.currentIndex = 1 - } + override fun setStartPoint(x: Float, y: Float, invalidate: Boolean) { + super.setStartPoint(x, y, invalidate) + calcCornerXY(x, y) + } + + override fun setTouchPoint(x: Float, y: Float, invalidate: Boolean) { + super.setTouchPoint(x, y, invalidate) + //触摸y中间位置吧y变成屏幕高度 + //触摸y中间位置吧y变成屏幕高度 + if (startY > pageView.height / 3.0 + && startY < pageView.height * 2 / 3.0 + || direction == Direction.PREV + ) { + touchY = pageView.height.toFloat() + } + + if (startY > pageView.height / 3.0 + && startY < pageView.height / 2.0 + && direction == Direction.NEXT + ) { + touchY = 1f } - curlView?.dispatchTouchEvent(event) - return super.onTouch(event) } override fun onScrollStart() { - } + val distanceX: Float + when (direction) { + Direction.NEXT -> distanceX = + if (isCancel) { + var dis = viewWidth - startX + touchX + if (dis > viewWidth) { + dis = viewWidth.toFloat() + } + viewWidth - dis + } else { + -(touchX + (viewWidth - startX)) + } + else -> distanceX = + if (isCancel) { + -(touchX - startX) + } else { + viewWidth - (touchX - startX) + } + } - override fun onDraw(canvas: Canvas) { + startScroll(touchX.toInt(), 0, distanceX.toInt(), 0) } override fun onScrollStop() { + curPage?.x = 0.toFloat() + if (!isCancel) { + pageView.fillPage(direction) + } } - override fun onScroll( - e1: MotionEvent, - e2: MotionEvent, - distanceX: Float, - distanceY: Float - ): Boolean { - if (!isMoved) { - val event = e1.toAction(MotionEvent.ACTION_UP) - curPage?.dispatchTouchEvent(event) - event.recycle() - if (abs(distanceX) > abs(distanceY)) { - if (distanceX < 0) { - //如果上一页不存在 - if (!hasPrev()) { - noNext = true - return true - } - //上一页截图 - bitmap = prevPage?.screenshot() - } else { - //如果不存在表示没有下一页了 - if (!hasNext()) { - noNext = true - return true - } - //下一页截图 - bitmap = nextPage?.screenshot() - } - isMoved = true + override fun onDraw(canvas: Canvas) { + bitmap?.let { + if (direction === Direction.NEXT) { + calcPoints() + drawCurrentPageArea(canvas, it, mPath0!!) //绘制翻页时的正面页 +// drawNextPageAreaAndShadow(canvas, it) + drawCurrentPageShadow(canvas) + drawCurrentBackArea(canvas, it) + } else { + calcPoints() + drawCurrentPageArea(canvas, it, mPath0!!) +// drawNextPageAreaAndShadow(canvas, it) + drawCurrentPageShadow(canvas) + drawCurrentBackArea(canvas, it) } } - if (isMoved) { - curlView?.canDraw = true - isCancel = if (pageView.isScrollDelegate) { - if (direction == Direction.NEXT) distanceY < 0 else distanceY > 0 + } + + /** + * 创建阴影的GradientDrawable + */ + private fun createDrawable() { + val color = intArrayOf(0x333333, -0x4fcccccd) + mFolderShadowDrawableRL = GradientDrawable( + GradientDrawable.Orientation.RIGHT_LEFT, color + ).apply { gradientType = GradientDrawable.LINEAR_GRADIENT } + mFolderShadowDrawableLR = GradientDrawable( + GradientDrawable.Orientation.LEFT_RIGHT, color + ).apply { gradientType = GradientDrawable.LINEAR_GRADIENT } + mBackShadowColors = intArrayOf(-0xeeeeef, 0x111111) + mBackShadowDrawableRL = GradientDrawable( + GradientDrawable.Orientation.RIGHT_LEFT, mBackShadowColors + ).apply { gradientType = GradientDrawable.LINEAR_GRADIENT } + mBackShadowDrawableLR = GradientDrawable( + GradientDrawable.Orientation.LEFT_RIGHT, mBackShadowColors + ).apply { gradientType = GradientDrawable.LINEAR_GRADIENT } + mFrontShadowColors = intArrayOf(-0x7feeeeef, 0x111111) + mFrontShadowDrawableVLR = GradientDrawable( + GradientDrawable.Orientation.LEFT_RIGHT, mFrontShadowColors + ).apply { gradientType = GradientDrawable.LINEAR_GRADIENT } + mFrontShadowDrawableVRL = GradientDrawable( + GradientDrawable.Orientation.RIGHT_LEFT, mFrontShadowColors + ).apply { gradientType = GradientDrawable.LINEAR_GRADIENT } + mFrontShadowDrawableHTB = GradientDrawable( + GradientDrawable.Orientation.TOP_BOTTOM, mFrontShadowColors + ).apply { gradientType = GradientDrawable.LINEAR_GRADIENT } + mFrontShadowDrawableHBT = GradientDrawable( + GradientDrawable.Orientation.BOTTOM_TOP, mFrontShadowColors + ).apply { gradientType = GradientDrawable.LINEAR_GRADIENT } + } + + /** + * 绘制翻起页背面 + */ + private fun drawCurrentBackArea( + canvas: Canvas, + bitmap: Bitmap + ) { + val i = (mBezierStart1.x + mBezierControl1.x).toInt() / 2 + val f1 = abs(i - mBezierControl1.x) + val i1 = (mBezierStart2.y + mBezierControl2.y).toInt() / 2 + val f2 = abs(i1 - mBezierControl2.y) + val f3 = min(f1, f2) + mPath1.reset() + mPath1.moveTo(mBezierVertex2.x, mBezierVertex2.y) + mPath1.lineTo(mBezierVertex1.x, mBezierVertex1.y) + mPath1.lineTo(mBezierEnd1.x, mBezierEnd1.y) + mPath1.lineTo(touchX, touchY) + mPath1.lineTo(mBezierEnd2.x, mBezierEnd2.y) + mPath1.close() + val mFolderShadowDrawable: GradientDrawable + val left: Int + val right: Int + if (mIsRT_LB) { + left = (mBezierStart1.x - 1).toInt() + right = (mBezierStart1.x + f3 + 1).toInt() + mFolderShadowDrawable = mFolderShadowDrawableLR!! + } else { + left = (mBezierStart1.x - f3 - 1).toInt() + right = (mBezierStart1.x + 1).toInt() + mFolderShadowDrawable = mFolderShadowDrawableRL!! + } + canvas.save() + try { + canvas.clipPath(mPath0) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + canvas.clipPath(mPath1) } else { - if (direction == Direction.NEXT) distanceX < 0 else distanceX > 0 + canvas.clipPath(mPath1, Region.Op.INTERSECT) } - isRunning = true - //设置触摸点 - setTouchPoint(e2.x, e2.y) + } catch (ignored: Exception) { } - return isMoved + mPaint!!.colorFilter = mColorMatrixFilter + val dis = hypot( + mCornerX - mBezierControl1.x.toDouble(), + mBezierControl2.y - mCornerY.toDouble() + ).toFloat() + val f8 = (mCornerX - mBezierControl1.x) / dis + val f9 = (mBezierControl2.y - mCornerY) / dis + mMatrixArray[0] = 1 - 2 * f9 * f9 + mMatrixArray[1] = 2 * f8 * f9 + mMatrixArray[3] = mMatrixArray[1] + mMatrixArray[4] = 1 - 2 * f8 * f8 + mMatrix.reset() + mMatrix.setValues(mMatrixArray) + mMatrix.preTranslate(-mBezierControl1.x, -mBezierControl1.y) + mMatrix.postTranslate(mBezierControl1.x, mBezierControl1.y) + canvas.drawBitmap(bitmap, mMatrix, mPaint) + mPaint.colorFilter = null + canvas.rotate(mDegrees, mBezierStart1.x, mBezierStart1.y) + mFolderShadowDrawable.setBounds( + left, mBezierStart1.y.toInt(), right, + (mBezierStart1.y + mMaxLength).toInt() + ) + mFolderShadowDrawable.draw(canvas) + canvas.restore() } - override fun onPageUp() { - curlView?.updatePages() - curlView?.requestRender() + /** + * 绘制翻起页的阴影 + */ + private fun drawCurrentPageShadow(canvas: Canvas) { + val degree: Double = if (mIsRT_LB) { + (Math.PI / 4 - atan2(mBezierControl1.y - touchX, touchY - mBezierControl1.x)) + } else { + (Math.PI / 4 - atan2(touchY - mBezierControl1.y, touchX - mBezierControl1.x)) + } + // 翻起页阴影顶点与touch点的距离 + val d1 = 25.toFloat() * 1.414 * cos(degree) + val d2 = 25.toFloat() * 1.414 * sin(degree) + val x = (touchX + d1).toFloat() + val y: Float + y = if (mIsRT_LB) { + (touchY + d2).toFloat() + } else { + (touchY - d2).toFloat() + } + mPath1.reset() + mPath1.moveTo(x, y) + mPath1.lineTo(touchX, touchY) + mPath1.lineTo(mBezierControl1.x, mBezierControl1.y) + mPath1.lineTo(mBezierStart1.x, mBezierStart1.y) + mPath1.close() + canvas.save() + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + canvas.clipOutPath(mPath0) + } else { + canvas.clipPath(mPath0, Region.Op.XOR) + } + canvas.clipPath(mPath1, Region.Op.INTERSECT) + } catch (ignored: java.lang.Exception) { + } + var leftX: Int + var rightX: Int + var mCurrentPageShadow: GradientDrawable + if (mIsRT_LB) { + leftX = mBezierControl1.x.toInt() + rightX = mBezierControl1.x.toInt() + 25 + mCurrentPageShadow = mFrontShadowDrawableVLR!! + } else { + leftX = (mBezierControl1.x - 25).toInt() + rightX = mBezierControl1.x.toInt() + 1 + mCurrentPageShadow = mFrontShadowDrawableVRL!! + } + var rotateDegrees: Float = + Math.toDegrees(atan2(touchX - mBezierControl1.x, mBezierControl1.y - touchY).toDouble()) + .toFloat() + canvas.rotate(rotateDegrees, mBezierControl1.x, mBezierControl1.y) + mCurrentPageShadow.setBounds( + leftX, + (mBezierControl1.y - mMaxLength).toInt(), rightX, + mBezierControl1.y.toInt() + ) + mCurrentPageShadow.draw(canvas) + canvas.restore() + mPath1.reset() + mPath1.moveTo(x, y) + mPath1.lineTo(touchX, touchY) + mPath1.lineTo(mBezierControl2.x, mBezierControl2.y) + mPath1.lineTo(mBezierStart2.x, mBezierStart2.y) + mPath1.close() + canvas.save() + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + canvas.clipOutPath(mPath0) + } else { + canvas.clipPath(mPath0, Region.Op.XOR) + } + canvas.clipPath(mPath1) + } catch (ignored: java.lang.Exception) { + } + if (mIsRT_LB) { + leftX = mBezierControl2.y.toInt() + rightX = (mBezierControl2.y + 25).toInt() + mCurrentPageShadow = mFrontShadowDrawableHTB!! + } else { + leftX = (mBezierControl2.y - 25).toInt() + rightX = (mBezierControl2.y + 1).toInt() + mCurrentPageShadow = mFrontShadowDrawableHBT!! + } + rotateDegrees = Math.toDegrees( + atan2(mBezierControl2.y - touchY, mBezierControl2.x - touchX).toDouble() + ).toFloat() + canvas.rotate(rotateDegrees, mBezierControl2.x, mBezierControl2.y) + val temp: Float = + if (mBezierControl2.y < 0) mBezierControl2.y - pageView.height else mBezierControl2.y + val hmg = hypot(mBezierControl2.x.toDouble(), temp.toDouble()).toInt() + if (hmg > mMaxLength) mCurrentPageShadow + .setBounds( + (mBezierControl2.x - 25).toInt() - hmg, leftX, + (mBezierControl2.x + mMaxLength).toInt() - hmg, + rightX + ) else mCurrentPageShadow.setBounds( + (mBezierControl2.x - mMaxLength).toInt(), leftX, + mBezierControl2.x.toInt(), rightX + ) + mCurrentPageShadow.draw(canvas) + canvas.restore() } - override fun pageChange(change: Int) { - pageView.post { - if (change > 0) { - pageView.moveToNextPage() + private fun drawNextPageAreaAndShadow( + canvas: Canvas, + bitmap: Bitmap + ) { + mPath1.reset() + mPath1.moveTo(mBezierStart1.x, mBezierStart1.y) + mPath1.lineTo(mBezierVertex1.x, mBezierVertex1.y) + mPath1.lineTo(mBezierVertex2.x, mBezierVertex2.y) + mPath1.lineTo(mBezierStart2.x, mBezierStart2.y) + mPath1.lineTo(mCornerX.toFloat(), mCornerY.toFloat()) + mPath1.close() + mDegrees = Math.toDegrees( + atan2( + (mBezierControl1.x - mCornerX).toDouble(), + mBezierControl2.y - mCornerY.toDouble() + ) + ).toFloat() + val leftX: Int + val rightY: Int + val mBackShadowDrawable: GradientDrawable + if (mIsRT_LB) { //左下及右上 + leftX = mBezierStart1.x.toInt() + rightY = (mBezierStart1.x + mTouchToCornerDis / 4).toInt() + mBackShadowDrawable = mBackShadowDrawableLR!! + } else { + leftX = (mBezierStart1.x - mTouchToCornerDis / 4).toInt() + rightY = mBezierStart1.x.toInt() + mBackShadowDrawable = mBackShadowDrawableRL!! + } + canvas.save() + try { + canvas.clipPath(mPath0) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + canvas.clipPath(mPath1) } else { - pageView.moveToPrevPage() + canvas.clipPath(mPath1, Region.Op.INTERSECT) } + //canvas.clipPath(mPath1, Region.Op.INTERSECT); + } catch (ignored: java.lang.Exception) { } + canvas.drawBitmap(bitmap, 0f, 0f, null) + canvas.rotate(mDegrees, mBezierStart1.x, mBezierStart1.y) + mBackShadowDrawable.setBounds( + leftX, mBezierStart1.y.toInt(), rightY, + (mMaxLength + mBezierStart1.y).toInt() + ) //左上及右下角的xy坐标值,构成一个矩形 + mBackShadowDrawable.draw(canvas) + canvas.restore() } - private inner class PageProvider : CurlView.PageProvider { + private fun drawCurrentPageArea( + canvas: Canvas, + bitmap: Bitmap, + path: Path + ) { + mPath0.reset() + mPath0.moveTo(mBezierStart1.x, mBezierStart1.y) + mPath0.quadTo( + mBezierControl1.x, mBezierControl1.y, mBezierEnd1.x, + mBezierEnd1.y + ) + mPath0.lineTo(touchX, touchY) + mPath0.lineTo(mBezierEnd2.x, mBezierEnd2.y) + mPath0.quadTo( + mBezierControl2.x, mBezierControl2.y, mBezierStart2.x, + mBezierStart2.y + ) + mPath0.lineTo(mCornerX.toFloat(), mCornerY.toFloat()) + mPath0.close() + canvas.save() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + canvas.clipOutPath(path) + } else { + canvas.clipPath(path, Region.Op.XOR) + } + canvas.drawBitmap(bitmap, 0f, 0f, null) + try { + canvas.restore() + } catch (e: java.lang.Exception) { + e.printStackTrace() + } + } - override val pageCount: Int - get() = 3 + /** + * 计算拖拽点对应的拖拽脚 + */ + private fun calcCornerXY(x: Float, y: Float) { + if (x <= pageView.width / 2.0) { + mCornerX = 0 + } else { + mCornerX = pageView.width + } + if (y <= pageView.height / 2.0) { + mCornerY = 0 + } else { + mCornerY = pageView.height + } + mIsRT_LB = (mCornerX == 0 && mCornerY == pageView.height + || mCornerX == pageView.width && mCornerY == 0) + } - override fun updatePage(page: CurlPage, width: Int, height: Int, index: Int) { - when (index) { - 0 -> page.setTexture(prevPage?.screenshot(), CurlPage.SIDE_BOTH) - 1 -> page.setTexture(curPage?.screenshot(), CurlPage.SIDE_BOTH) - 2 -> page.setTexture(nextPage?.screenshot(), CurlPage.SIDE_BOTH) + private fun calcPoints() { + mMiddleX = (touchX + mCornerX) / 2 + mMiddleY = (touchY + mCornerY) / 2 + mBezierControl1.x = + mMiddleX - (mCornerY - mMiddleY) * (mCornerY - mMiddleY) / (mCornerX - mMiddleX) + mBezierControl1.y = mCornerY.toFloat() + mBezierControl2.x = mCornerX.toFloat() + val f4 = mCornerY - mMiddleY + if (f4 == 0f) { + mBezierControl2.y = mMiddleY - (mCornerX - mMiddleX) * (mCornerX - mMiddleX) / 0.1f + } else { + mBezierControl2.y = + mMiddleY - (mCornerX - mMiddleX) * (mCornerX - mMiddleX) / (mCornerY - mMiddleY) + } + mBezierStart1.x = mBezierControl1.x - (mCornerX - mBezierControl1.x) / 2 + mBezierStart1.y = mCornerY.toFloat() + // 当mBezierStart1.x < 0或者mBezierStart1.x > 480时 + // 如果继续翻页,会出现BUG故在此限制 + if (touchX > 0 && touchX < pageView.width) { + if (mBezierStart1.x < 0 || mBezierStart1.x > pageView.width) { + if (mBezierStart1.x < 0) mBezierStart1.x = pageView.width - mBezierStart1.x + val f1: Float = abs(mCornerX - touchX) + val f2: Float = pageView.width * f1 / mBezierStart1.x + touchX = abs(mCornerX - f2) + val f3: Float = abs(mCornerX - touchX) * abs(mCornerY - touchX) / f1 + touchX = abs(mCornerY - f3) + mMiddleX = (touchX + mCornerX) / 2 + mMiddleY = (touchY + mCornerY) / 2 + mBezierControl1.x = + mMiddleX - (mCornerY - mMiddleY) * (mCornerY - mMiddleY) / (mCornerX - mMiddleX) + mBezierControl1.y = mCornerY.toFloat() + mBezierControl2.x = mCornerX.toFloat() + val f5 = mCornerY - mMiddleY + if (f5 == 0f) { + mBezierControl2.y = + mMiddleY - (mCornerX - mMiddleX) * (mCornerX - mMiddleX) / 0.1f + } else { + mBezierControl2.y = + mMiddleY - (mCornerX - mMiddleX) * (mCornerX - mMiddleX) / (mCornerY - mMiddleY) + } + mBezierStart1.x = (mBezierControl1.x + - (mCornerX - mBezierControl1.x) / 2) } } + mBezierStart2.x = mCornerX.toFloat() + mBezierStart2.y = mBezierControl2.y - (mCornerY - mBezierControl2.y) / 2 + mTouchToCornerDis = hypot(touchX - mCornerX, touchY - mCornerY) + mBezierEnd1 = getCross( + PointF(touchX, touchY), mBezierControl1, mBezierStart1, + mBezierStart2 + ) + mBezierEnd2 = getCross( + PointF(touchX, touchY), mBezierControl2, mBezierStart1, + mBezierStart2 + ) + mBezierVertex1.x = (mBezierStart1.x + 2 * mBezierControl1.x + mBezierEnd1.x) / 4 + mBezierVertex1.y = (2 * mBezierControl1.y + mBezierStart1.y + mBezierEnd1.y) / 4 + mBezierVertex2.x = (mBezierStart2.x + 2 * mBezierControl2.x + mBezierEnd2.x) / 4 + mBezierVertex2.y = (2 * mBezierControl2.y + mBezierStart2.y + mBezierEnd2.y) / 4 } - // 定义书籍尺寸的变化监听器 - private inner class SizeChangedObserver : CurlView.SizeChangedObserver { - override fun onSizeChanged(width: Int, height: Int) { - curlView?.setViewMode(CurlView.SHOW_ONE_PAGE) - } + /** + * 求解直线P1P2和直线P3P4的交点坐标 + */ + private fun getCross(P1: PointF, P2: PointF, P3: PointF, P4: PointF): PointF { + val crossP = PointF() + // 二元函数通式: y=ax+b + val a1 = (P2.y - P1.y) / (P2.x - P1.x) + val b1 = (P1.x * P2.y - P2.x * P1.y) / (P1.x - P2.x) + val a2 = (P4.y - P3.y) / (P4.x - P3.x) + val b2 = (P3.x * P4.y - P4.x * P3.y) / (P3.x - P4.x) + crossP.x = (b2 - b1) / (a1 - a2) + crossP.y = a1 * crossP.x + b1 + return crossP } } \ No newline at end of file