package org.ebookdroid.core.curl; import org.ebookdroid.common.settings.books.BookSettings; import org.ebookdroid.core.EventGLDraw; import org.ebookdroid.core.Page; import org.ebookdroid.core.SinglePageController; import org.ebookdroid.core.ViewState; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.view.MotionEvent; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.emdev.ui.gl.GLCanvas; public abstract class AbstractPageAnimator extends SinglePageView implements PageAnimator { protected static final Paint PAINT = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); /** Fixed update time used to create a smooth curl animation */ protected int mUpdateRate; /** Handler used to auto flip time based */ protected FlipAnimationHandler mAnimationHandler; /** Point used to move */ protected Vector2D mMovement; /** Defines the flip direction that is currently considered */ protected boolean bFlipRight; /** If TRUE we are currently auto-flipping */ protected boolean bFlipping; /** Used to control touch input blocking */ protected boolean bBlockTouchInput = false; /** Enable input after the next draw event */ protected boolean bEnableInputAfterDraw = false; protected final Vector2D mA = new Vector2D(0, 0); /** The initial offset for x and y axis movements */ protected int mInitialEdgeOffset; /** The finger position */ protected Vector2D mFinger; /** Movement point form the last frame */ protected Vector2D mOldMovement; /** TRUE if the user moves the pages */ protected boolean bUserMoves; protected final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); public AbstractPageAnimator(final PageAnimationType type, final SinglePageController singlePageDocumentView) { super(type, singlePageDocumentView); } /** * {@inheritDoc} * * @see org.ebookdroid.core.curl.SinglePageView#init() */ @Override public void init() { super.init(); mMovement = new Vector2D(0, 0); mFinger = new Vector2D(0, 0); mOldMovement = new Vector2D(0, 0); // Create our curl animation handler mAnimationHandler = new FlipAnimationHandler(this); // Set the default props mUpdateRate = 5; } /** * {@inheritDoc} * * @see org.ebookdroid.core.curl.SinglePageView#isPageVisible(org.ebookdroid.core.Page, * org.ebookdroid.core.ViewState) */ @Override public boolean isPageVisible(final Page page, final ViewState viewState) { final int pageIndex = page.index.viewIndex; return pageIndex == this.foreIndex || pageIndex == this.backIndex; } private boolean isRightToLeft() { final BookSettings bs = view.base.getBookSettings(); return bs.rtl; } /** * Swap to next (more accurately, right) view */ protected ViewState nextView(final ViewState viewState) { if (viewState.model == null) { return viewState; } final int pageCount = viewState.model.getPageCount(); if (pageCount == 0) { return viewState; } if (isRightToLeft()) { foreIndex = viewState.pages.currentIndex % pageCount; backIndex = (pageCount + viewState.pages.currentIndex - 1) % pageCount; } else { foreIndex = viewState.pages.currentIndex % pageCount; backIndex = (foreIndex + 1) % pageCount; } final Page forePage = viewState.model.getPageObject(foreIndex); final Page backPage = viewState.model.getPageObject(backIndex); return view.invalidatePages(viewState, forePage, backPage); } /** * Swap to previous (more accurately, left) view */ protected ViewState previousView(final ViewState viewState) { if (viewState.model == null) { return viewState; } final int pageCount = viewState.model.getPageCount(); if (pageCount == 0) { return viewState; } if (isRightToLeft()) { backIndex = viewState.pages.currentIndex % pageCount; foreIndex = (viewState.pages.currentIndex + 1) % pageCount; } else { backIndex = viewState.pages.currentIndex % pageCount; foreIndex = (pageCount + backIndex - 1) % pageCount; } final Page forePage = viewState.model.getPageObject(foreIndex); final Page backPage = viewState.model.getPageObject(backIndex); return view.invalidatePages(viewState, forePage, backPage); } /** * {@inheritDoc} * * @see org.ebookdroid.core.curl.SinglePageView#flipAnimationStep() */ @Override public synchronized void flipAnimationStep() { if (!bFlipping) { return; } // System.out.println("FAS start"); final int width = view.getWidth(); // No input when flipping bBlockTouchInput = true; // Handle speed float curlSpeed = width / 5; if (!bFlipRight) { curlSpeed *= -1; } // Move us mMovement.x += curlSpeed; mMovement = fixMovement(mMovement, false); // Create values lock.writeLock().lock(); try { updateValues(); if (mA.x < getLeftBound()) { mA.x = getLeftBound() - 1; } if (mA.x > width - 1) { mA.x = width; } } finally { lock.writeLock().unlock(); } // Check for endings :D if (mA.x <= getLeftBound() || mA.x >= width - 1) { bFlipping = false; // System.out.println("FAS end"); if (bFlipRight) { view.goToPage(backIndex); foreIndex = backIndex; } else { view.goToPage(foreIndex); backIndex = foreIndex; } // Create values lock.writeLock().lock(); try { resetClipEdge(); updateValues(); } finally { lock.writeLock().unlock(); } // Enable touch input after the next draw event bEnableInputAfterDraw = true; } else { mAnimationHandler.sleep(mUpdateRate); } // Force a new draw call view.redrawView(); } protected float getLeftBound() { return 1; } protected abstract void resetClipEdge(); protected abstract Vector2D fixMovement(Vector2D point, final boolean bMaintainMoveDir); protected abstract void drawBackground(EventGLDraw event); protected abstract void drawForeground(EventGLDraw event); protected abstract void drawExtraObjects(EventGLDraw event); /** * Update points values values. */ protected abstract void updateValues(); /** * {@inheritDoc} * * @see org.ebookdroid.core.curl.SinglePageView#draw(org.ebookdroid.core.EventGLDraw) */ @Override public final synchronized void draw(final EventGLDraw event) { final GLCanvas canvas = event.canvas; final ViewState viewState = event.viewState; if (!enabled()) { super.draw(event); return; } // We need to initialize all size data when we first draw the view if (!isViewDrawn()) { setViewDrawn(true); onFirstDrawEvent(canvas, viewState); } canvas.clearBuffer(Color.BLACK); // Draw our elements lock.readLock().lock(); try { drawInternal(event); drawExtraObjects(event); } finally { lock.readLock().unlock(); } // Check if we can re-enable input if (bEnableInputAfterDraw) { bBlockTouchInput = false; bEnableInputAfterDraw = false; } } protected void drawInternal(final EventGLDraw event) { drawForeground(event); if (foreIndex != backIndex) { drawBackground(event); } } protected abstract void onFirstDrawEvent(GLCanvas canvas, final ViewState viewState); /** * {@inheritDoc} * * @see org.ebookdroid.core.curl.SinglePageView#enabled() */ @Override public final boolean enabled() { final Rect limits = view.getScrollLimits(); return limits.width() <= 0 && limits.height() <= 0; } /** * {@inheritDoc} * * @see org.ebookdroid.core.curl.SinglePageView#onTouchEvent(android.view.MotionEvent) */ @Override public final boolean onTouchEvent(final MotionEvent event) { if (bBlockTouchInput) { return true; } // Get our finger position mFinger.x = event.getX(); mFinger.y = event.getY(); final int width = view.getWidth(); ViewState viewState = ViewState.get(view); try { // Depending on the action do what we need to switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mOldMovement.x = mFinger.x; mOldMovement.y = mFinger.y; bUserMoves = false; return false; case MotionEvent.ACTION_UP: if (bUserMoves) { bUserMoves = false; bFlipping = true; flipAnimationStep(); } else { return false; } break; case MotionEvent.ACTION_MOVE: if ((mFinger.absdistancex(mOldMovement) > 25)) { if (!bUserMoves) { // Check current and start positions if (mFinger.x < mOldMovement.x) { mMovement.x = mInitialEdgeOffset; mMovement.y = mInitialEdgeOffset; // Set the right movement flag bFlipRight = true; viewState = nextView(viewState); } else { // Set the left movement flag bFlipRight = false; // go to next previous page viewState = previousView(viewState); // Set new movement mMovement.x = getInitialXForBackFlip(width); mMovement.y = mInitialEdgeOffset; } } bUserMoves = true; } else { if (!bUserMoves) { break; } } // Get movement mMovement.x -= mFinger.x - mOldMovement.x; mMovement.y -= mFinger.y - mOldMovement.y; mMovement = fixMovement(mMovement, true); // Make sure the y value get's locked at a nice level if (mMovement.y <= 1) { mMovement.y = 1; } // Get movement direction if ((mFinger.absdistancex(mOldMovement) > 25)) { if (mFinger.x < mOldMovement.x) { bFlipRight = true; } else { bFlipRight = false; } } // Save old movement values mOldMovement.x = mFinger.x; mOldMovement.y = mFinger.y; // Force a new draw call lock.writeLock().lock(); try { updateValues(); } finally { lock.writeLock().unlock(); } view.redrawView(viewState); return !bUserMoves; } } finally { viewState.release(); } // TODO: Only consume event if we need to. return true; } protected int getInitialXForBackFlip(final int width) { return width; } /** * {@inheritDoc} * * @see org.ebookdroid.core.curl.SinglePageView#animate(int) */ @Override public void animate(final int direction) { resetClipEdge(); mMovement = new Vector2D(direction < 0 ? 7 * view.getWidth() / 8 : view.getWidth() / 8, mInitialEdgeOffset); bFlipping = true; bFlipRight = direction > 0; final ViewState viewState = ViewState.get(view); final ViewState newState = bFlipRight ? nextView(viewState) : previousView(viewState); bUserMoves = false; flipAnimationStep(); viewState.release(); newState.release(); } }