/******************************************************************************* * Copyright (c) 2011 Pieter Pareit. * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * Contributors: * Pieter Pareit - initial API and implementation ******************************************************************************/ package be.ppareit.nanopond; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Paint; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.ScaleGestureDetector; import android.view.WindowManager; import net.vrallev.android.cat.Cat; import be.ppareit.android.GameLoopView; import be.ppareit.nanopond.NanoPond.Cell; /** * */ public class NanoPondView extends GameLoopView { private static final float MIN_SCALE = 1.0f; private static final float MAX_SCALE = 40.0f; public enum State { RUNNING, PAUSED, } private State mState = State.PAUSED; private NanoPond mNanoPond = null; private Paint mCanvasPaint = null; private Paint mBackgroundPaint = null; private final Paint mCellPaint = new Paint(); private final Paint mActiveCellPaint = new Paint(); // Useful paint to display debugging info on screen private final Paint mDebugPaint = new Paint(); private GestureDetector mMoveDetector; private ScaleGestureDetector mScaleDetector; private Matrix mDrawMatrix = new Matrix(); // keep track of the active cell private int mActiveCellCol = 0; private int mActiveCellRow = 0; public NanoPondView(Context context, AttributeSet attrs) { super(context, attrs); if (isInEditMode()) return; mNanoPond = ((NanoPondActivity) context).getNanoPond(); mCanvasPaint = new Paint(); mCanvasPaint.setColor(Color.GRAY); mBackgroundPaint = new Paint(); mBackgroundPaint.setColor(Color.BLACK); mDebugPaint.setARGB(255, 255, 240, 0); mDebugPaint.setTextSize(32); mActiveCellPaint.setColor(Color.CYAN); setTargetFps(0); mMoveDetector = new GestureDetector(context, new MoveListener()); mScaleDetector = new ScaleGestureDetector(context, new ScaleListener()); } public void setMode(State mode) { if (mode == State.RUNNING && mState != State.RUNNING) { startGameLoop(); mState = State.RUNNING; } else if (mode == State.PAUSED && mState != State.PAUSED) { pauseGameLoop(); mState = State.PAUSED; } } public boolean isCellActive() { return (mActiveCellCol != -1); } public int getActiveCellCol() { if (mActiveCellCol == -1) throw new IllegalStateException("No cell active"); return mActiveCellCol; } public int getActiveCellRow() { if (mActiveCellRow == -1) throw new IllegalStateException("No cell active"); return mActiveCellRow; } @Override protected void onUpdate() { // the nanopond object runs in its own thread thus that is updated continuously } private class MoveListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { mDrawMatrix.postTranslate(-distanceX, -distanceY); return true; } @Override public boolean onSingleTapUp(MotionEvent e) { float[] pts = { e.getX(), e.getY() }; Matrix inverse = new Matrix(); mDrawMatrix.invert(inverse); inverse.mapPoints(pts); final int c = (int) pts[0]; final int r = (int) pts[1]; Cat.d("Tapped: " + c + " " + r); if (0 <= c && c < NanoPond.POND_SIZE_X && 0 <= r && r < NanoPond.POND_SIZE_Y) { mActiveCellCol = c; mActiveCellRow = r; } return true; } } private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { @Override public boolean onScale(ScaleGestureDetector detector) { float factor = detector.getScaleFactor(); // limit zooming final float scale = factor * getScale(); if (scale < MIN_SCALE || MAX_SCALE < scale) { return false; } final float focusX = detector.getFocusX(); final float focusY = detector.getFocusY(); mDrawMatrix.postScale(factor, factor, focusX, focusY); Cat.d("New scale: " + getScale()); return true; } } private float getScale() { return mDrawMatrix.mapRadius(1.f); } @Override public boolean onTouchEvent(MotionEvent event) { mScaleDetector.onTouchEvent(event); mMoveDetector.onTouchEvent(event); invalidate(); return true; } @Override protected void onDraw(Canvas canvas) { if (isInEditMode()) return; final Cell[][] pond = mNanoPond.pond; final int cols = NanoPond.POND_SIZE_X; final int rows = NanoPond.POND_SIZE_Y; final float size = 1; canvas.drawRect(0, 0, getWidth(), getHeight(), mCanvasPaint); canvas.save(); canvas.concat(mDrawMatrix); canvas.drawRect(0, 0, cols * size, rows * size, mBackgroundPaint); // draw all the individual cells for (int c = 0; c < cols; c++) { for (int r = 0; r < rows; r++) { final Cell cell = pond[c][r]; if (cell.generation > 2 && cell.energy > 0) { float left = (c * size); float top = (r * size); float right = (c * size + size); float bottom = (r * size + size); mCellPaint.setColor(getColor(cell)); canvas.drawRect(left, top, right, bottom, mCellPaint); } } } // draw the active cell if (mActiveCellCol != -1) { float left = (mActiveCellCol * size); float top = (mActiveCellRow * size); float right = (mActiveCellCol * size + size); float bottom = (mActiveCellRow * size + size); canvas.drawRect(left, top, right, bottom, mActiveCellPaint); left += size/8; top += size/8; right -= size/8; bottom -= size/8; final Cell cell = pond[mActiveCellCol][mActiveCellRow]; if (cell.generation > 2 && cell.energy > 0) { mCellPaint.setColor(getColor(cell)); canvas.drawRect(left, top, right, bottom, mCellPaint); } else { canvas.drawRect(left, top, right, bottom, mBackgroundPaint); } } canvas.restore(); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { if (isInEditMode()) return; Cat.d("size changed"); mDrawMatrix.postTranslate(-NanoPond.POND_SIZE_X / 2, -NanoPond.POND_SIZE_Y / 2); // scale to display metrics DisplayMetrics metrics = new DisplayMetrics(); WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); wm.getDefaultDisplay().getMetrics(metrics); final float scale = 4 * metrics.density; mDrawMatrix.postScale(scale, scale); Cat.d("Size changed, new scale: " + scale); // move left-top position to middle of screen mDrawMatrix.postTranslate(getWidth() / 2, getHeight() / 2); } static int[] artificial = { Color.WHITE, Color.GREEN, Color.CYAN, Color.YELLOW, Color.RED, Color.MAGENTA }; static int cap(int i) { if (i > 255) return 255; else if (i < 0) return 0; else return i; } static int getColor(Cell cell) { if (cell.lineage < 0) return artificial[(int) Math.abs(cell.lineage) % artificial.length]; int alpha = 0xff; int red = (int) cell.lineage % 256; int green = (int) cell.lineage % (256 * 256) / 256; int blue = (int) cell.lineage % (256 * 256 * 256) / 256 / 256; return (alpha << 24) | (cap(red) << 16) | (cap(green) << 8) | cap(blue); } }