package com.xenoage.zong.android.scoreview; import static com.xenoage.utils.PlatformUtils.platformUtils; import static com.xenoage.utils.android.AndroidPlatformUtils.io; import static com.xenoage.utils.android.io.AndroidIO.*; import static com.xenoage.utils.kernel.Range.range; import static com.xenoage.zong.android.renderer.AndroidLayoutRenderer.androidLayoutRenderer; import java.io.IOException; import java.util.ArrayList; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.NinePatchDrawable; import android.view.MotionEvent; import android.view.View; import com.xenoage.utils.android.AndroidPlatformUtils; import com.xenoage.utils.android.io.AndroidIO; import com.xenoage.utils.jse.io.JseInputStream; import com.xenoage.utils.math.Units; import com.xenoage.utils.math.geom.Point2f; import com.xenoage.utils.math.geom.Rectangle2i; import com.xenoage.utils.math.geom.Size2i; import com.xenoage.zong.android.R; import com.xenoage.zong.android.renderer.AndroidLayoutRenderer; import com.xenoage.zong.documents.ScoreDoc; import com.xenoage.zong.layout.Layout; import com.xenoage.zong.view.PageViewManager; import com.xenoage.zong.view.ViewState; /** * This class provides a scrollable (by dragging) and zoomable (by using * the zoom buttons) view on a {@link ScoreDoc}. * * @author Andreas Wenger */ public class ScorePageView extends View { private Layout layout; //view parameters private float scaling; private Point2f scrollMm = new Point2f(0, 0); private PageViewManager pageViewManager; private Bitmap desktopBitmap; private NinePatchDrawable page9Patch; private int page9PatchOffsetLeft = 1, page9PatchOffsetTop = 1, page9PatchOffsetRight = 19, page9PatchOffsetBottom = 19; private Point2f scrollLastPx = new Point2f(0, 0); //currently loaded tiles private ArrayList<Bitmap> pagesBitmaps = new ArrayList<Bitmap>(); private int pagePreloadDistance = 200; private int pageUnloadDistance = 400; public ScorePageView(Context context, float scaling) { super(context); this.scaling = scaling; } public ScorePageView(Context context) { this(context, 1); } public void setScoreDoc(ScoreDoc doc) { this.layout = doc.getLayout(); this.pageViewManager = new PageViewManager(layout); //load desktop and page image try { desktopBitmap = BitmapFactory.decodeStream(io().openFile("data/img/desktop/desktop.png")); page9Patch = (NinePatchDrawable) getContext().getResources().getDrawable(R.drawable.page); } catch (IOException ex) { } //register touch listener for scrolling setOnTouchListener((view, event) -> { Point2f currentPx = new Point2f(event.getX(), event.getY()); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: scrollLastPx = new Point2f(event.getX(), event.getY()); break; case MotionEvent.ACTION_MOVE: case MotionEvent.ACTION_UP: Point2f distancePx = scrollLastPx.sub(currentPx); Point2f distanceMm = new Point2f(Units.pxToMm(distancePx.x, scaling), Units.pxToMm( distancePx.y, scaling)); scrollMm = scrollMm.add(distanceMm); scrollLastPx = currentPx; view.postInvalidate(); break; } return true; }); } public void setScaling(float scaling) { this.scaling = scaling; pagesBitmaps.clear(); postInvalidate(); } private ViewState getViewState() { return new ViewState(new Size2i(getWidth(), getHeight()), scaling, scrollMm); } @Override protected void onDraw(Canvas canvas) { int w = getWidth(); int h = getHeight(); Rect viewRect = new Rect(0, 0, w, h); Rectangle2i viewRectangle2i = new Rectangle2i(0, 0, w, h); //cleanup tiles cleanupPages(); //paint desktop canvas.drawBitmap(desktopBitmap, null, viewRect, null); //paint pages for (int iPage : range(layout.getPages())) { Rectangle2i pageRect = pageViewManager.computePageRect(iPage, getViewState()); //if page is invisible, ingore page if (!isRectVisible(viewRectangle2i, pageRect, 30)) //30: for shadow... continue; canvas.save(); canvas.translate(pageRect.x1(), pageRect.y1()); //page background page9Patch.setBounds(-page9PatchOffsetLeft, -page9PatchOffsetTop, pageRect.width() + page9PatchOffsetRight, pageRect.height() + page9PatchOffsetBottom); page9Patch.draw(canvas); //frames if (isPageVisible(iPage, pagePreloadDistance)) { Bitmap pageBitmap = getPageBitmap(iPage); canvas.drawBitmap(pageBitmap, null, new RectF(0, 0, pageBitmap.getWidth(), pageBitmap.getHeight()), null); } canvas.restore(); } //TEST //Rectangle2i pageRect = pageViewManager.computePageRect(0, getViewState()); //canvas.drawBitmap(firstPageBitmap, null, new RectF(pageRect.x1(), pageRect.y1(), // pageRect.x2(), pageRect.y2()), null); /* //TEST: paint a line from the top left corner to the bottom right corner Paint paintLine = new Paint(); paintLine.setStyle(Paint.Style.STROKE); paintLine.setColor(Color.WHITE); canvas.drawLine(0, 0, getWidth(), getHeight(), paintLine); */ } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //maximize in parent (if width and height are fill_parent) super.onMeasure(widthMeasureSpec, heightMeasureSpec); setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight()); } /** * Removes all pages, that are currently not visible. */ private void cleanupPages() { for (int iPage : range(layout.getPages())) { if (!isPageVisible(iPage, pageUnloadDistance)) pagesBitmaps.set(iPage, null); } } /** * Gets the {@link Bitmap} of the given page. * If it is not loaded yet, it is generated and then returned. * The method blocks until the bitmap is ready. */ private Bitmap getPageBitmap(int pageIndex) { Bitmap ret = pagesBitmaps.get(pageIndex); if (ret == null) { ret = androidLayoutRenderer.paint(layout, pageIndex, scaling); pagesBitmaps.set(pageIndex, ret); } return ret; } /** * Returns true, if the page with the given index * is currently visible (with respect to the given additional distance). */ private boolean isPageVisible(int pageIndex, int additionalDistance) { Rectangle2i viewRect = new Rectangle2i(-additionalDistance, -additionalDistance, getWidth() + 2 * additionalDistance, getHeight() + 2 * additionalDistance); Rectangle2i pageRect = pageViewManager.computePageRect(pageIndex, getViewState()); return (isRectVisible(viewRect, pageRect, 0)); } /** * Returns true, if the given rectangle is partially * or fully visible on the view rect, otherwise false. */ private boolean isRectVisible(Rectangle2i viewRect, Rectangle2i rect, int additionalDistance) { int ad = additionalDistance; return (rect.x2() + ad >= viewRect.x1() && rect.x1() - ad <= viewRect.x2() && rect.y2() + ad >= viewRect.y1() && rect.y1() - ad <= viewRect.y2()); } }