/*
* Protocoder
* A prototyping platform for Android devices
*
* Victor Diaz Barrales victormdb@gmail.com
*
* Copyright (C) 2014 Victor Diaz
* Copyright (C) 2013 Motorola Mobility LLC
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software
* is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package org.protocoderrunner.apprunner.api.widgets;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ComposeShader;
import android.graphics.DashPathEffect;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathEffect;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.SweepGradient;
import android.graphics.Typeface;
import android.view.MotionEvent;
import android.view.View;
import org.protocoderrunner.apprunner.api.PUtil;
import org.protocoderrunner.project.ProjectManager;
import org.protocoderrunner.sensors.WhatIsRunning;
import org.protocoderrunner.utils.Image;
import org.protocoderrunner.utils.MLog;
import java.io.File;
import java.util.Vector;
import static android.graphics.Shader.TileMode;
public class PCanvasView extends View implements PViewInterface {
private static final String TAG = "PCanvasView";
public PorterDuff.Mode FILTER_ADD = PorterDuff.Mode.ADD;
public PorterDuff.Mode FILTER_XOR = PorterDuff.Mode.XOR;
public PorterDuff.Mode FILTER_CLEAR = PorterDuff.Mode.CLEAR;
public PorterDuff.Mode FILTER_LIGHTEN = PorterDuff.Mode.LIGHTEN;
public PorterDuff.Mode FILTER_MULTIPLY = PorterDuff.Mode.MULTIPLY;
public PorterDuff.Mode FILTER_SCREEN = PorterDuff.Mode.SCREEN;
public PorterDuff.Mode FILTER_OVERLAY = PorterDuff.Mode.OVERLAY;
public PorterDuff.Mode FILTER_DARKEN = PorterDuff.Mode.DARKEN;
public PorterDuff.Mode FILTER_DST = PorterDuff.Mode.DST;
public PorterDuff.Mode FILTER_DST_ATOP = PorterDuff.Mode.DST_ATOP;
public PorterDuff.Mode FILTER_DST_IN = PorterDuff.Mode.DST_IN;
public PorterDuff.Mode FILTER_DST_OUT = PorterDuff.Mode.DST_OUT;
public PorterDuff.Mode FILTER_DST_OVER = PorterDuff.Mode.DST_OVER;
public PorterDuff.Mode FILTER_SRC = PorterDuff.Mode.SRC;
public PorterDuff.Mode FILTER_SRC_ATOP = PorterDuff.Mode.SRC_ATOP;
public PorterDuff.Mode FILTER_SRC_IN = PorterDuff.Mode.SRC_IN;
public PorterDuff.Mode FILTER_SRC_OUT = PorterDuff.Mode.SRC_OUT;
public PorterDuff.Mode FILTER_SRC_OVER = PorterDuff.Mode.SRC_OVER;
public TileMode MODE_CLAMP = Shader.TileMode.CLAMP;
public TileMode MODE_MIRROR = TileMode.MIRROR;
public TileMode MODE_REPEAT = TileMode.REPEAT;
public boolean MODE_CORNER = true;
public boolean MODE_CENTER = false;
public Paint.Align ALIGN_CENTER = Paint.Align.CENTER;
public Paint.Align ALIGN_LEFT = Paint.Align.LEFT;
public Paint.Align ALIGN_RIGHT = Paint.Align.RIGHT;
public Paint.Cap CAP_ROUND = Paint.Cap.ROUND;
public Paint.Cap CAP_BUTT = Paint.Cap.BUTT;
public Paint.Cap CAP_SQUARE = Paint.Cap.SQUARE;
private final Context context;
private PUtil.Looper loop;
private Paint mPaintFill;
private Paint mPaintStroke;
private Paint mPaintBackground;
private boolean mAutoDraw = false;
private boolean mModeCorner = MODE_CORNER;
private boolean strokeOn = false;
private boolean fillOn = true;
private boolean viewIsInit = false;
private int mWidth;
private int mHeight;
public interface PCanvasInterfaceDraw {
void onDraw(Canvas c);
}
public interface PCanvasInterfaceTouch {
void onTouch(float x, float y);
}
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Canvas mCanvas;
private Bitmap mCurrentBmp;
private Vector<Layer> mLayerFifo;
private PCanvasInterfaceDraw pCanvasInterfaceDraw;
private PCanvasInterfaceTouch pCanvasInterfaceTouch;
private int currentLayer = -1;
public void init() {
MLog.d(TAG, "init");
WhatIsRunning.getInstance().add(this);
mPaintFill = new Paint();
mPaintStroke = new Paint();
mPaintFill.setAntiAlias(true);
mPaintStroke.setAntiAlias(true);
mPaintBackground = new Paint();
}
public void initLayers() {
//initLayers
MLog.d(TAG, "initLayers");
mLayerFifo = new Vector<Layer>();
Layer layer = createNewLayer();
layer.visible = true;
mCurrentBmp = layer.bmp;
mLayerFifo.add(++currentLayer, layer);
mCanvas = new Canvas(mCurrentBmp);
draw(mCanvas);
viewIsInit = true;
MLog.d(TAG, "viewIsInit " + viewIsInit);
}
public PCanvasView(Context context) {
super(context);
MLog.d(TAG, "onCreate");
this.context = context;
init();
initLayers();
}
public PCanvasView(Context context, int w, int h) {
super(context);
MLog.d(TAG, "onCreate");
mWidth = w;
mHeight = h;
this.context = context;
init();
initLayers();
}
@Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
MLog.d(TAG, "onMeasure");
setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
}
//on draw
@Override
protected synchronized void onDraw(Canvas c) {
MLog.d(TAG, "onDraw " + viewIsInit);
if (viewIsInit) {
super.onDraw(c);
//draw all the layers
for (Layer layer : mLayerFifo) {
if(layer.visible) {
c.drawBitmap(layer.bmp, 0, 0, null);
}
}
}
}
//on touch
@Override
public boolean onTouchEvent(MotionEvent event) {
if (pCanvasInterfaceTouch != null) {
pCanvasInterfaceTouch.onTouch(event.getX(), event.getY());
}
return super.onTouchEvent(event);
}
public void draw(PCanvasInterfaceDraw pCanvasInterface) {
pCanvasInterface.onDraw(mCanvas);
}
public void onTouch(PCanvasInterfaceTouch pCanvasInterfaceTouch) {
this.pCanvasInterfaceTouch = pCanvasInterfaceTouch;
}
public void loopDraw(int ms, final PCanvasInterfaceDraw pCanvasInterfaceDraw) {
if (loop != null) {
loop.stop();
loop = null;
}
PUtil util = new PUtil((Activity) context);
loop = util.loop(ms, new PUtil.LooperCB() {
@Override
public void event() {
pCanvasInterfaceDraw.onDraw(mCanvas);
invalidate();
}
});
mAutoDraw = true;
}
public void refresh() {
if (mAutoDraw) {
invalidate();
}
}
public void autoDraw(boolean b) {
mAutoDraw = b;
}
public Canvas getCanvas() {
return mCanvas;
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
MLog.d(TAG, "onAttachedToWindow");
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
MLog.d(TAG, "onSizeChanged " + getWidth() + " " + getHeight());
//enable this
//Bitmap.Config conf = Bitmap.Config.ARGB_8888;
//Bitmap _bmp = Bitmap.createBitmap(getWidth(), getHeight(), conf);
//mCurrentBmp = _bmp;
//mCanvas = new Canvas(mCurrentBmp);
//draw(mCanvas);
//mLayerFifo.get(0).bmp = _bmp;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
MLog.d(TAG, "onLayout");
//init();
}
@Override
protected void onDetachedFromWindow() {
MLog.d(TAG, "onDetachedFromwindow");
stop();
super.onDetachedFromWindow();
}
public void stop() {
if (loop != null) {
loop.stop();
}
}
//TODO drawPaint o drawARGB
public PCanvasView background(int r, int g, int b, int alpha) {
mPaintBackground.setStyle(Paint.Style.FILL);
mPaintBackground.setARGB(alpha, r, g, b);
mCanvas.drawRect(0, 0, mWidth, mHeight, mPaintBackground);
refresh();
return this;
}
//TODO drawPaint o drawARGB
public PCanvasView background(int r, int g, int b) {
background(r, g, b, 255);
refresh();
return this;
}
public PCanvasView point(float x, float y) {
mCanvas.drawPoint(x, y, mPaintStroke);
refresh();
return this;
}
//TODO
public PCanvasView points(float[] points) {
mCanvas.drawPoints(points, mPaintStroke);
refresh();
return this;
}
//TODO
public PCanvasView points(float[] points, int offset, int count) {
mCanvas.drawPoints(points, offset, count, mPaintStroke);
refresh();
return this;
}
public PCanvasView line(float x1, float y1, float x2, float y2) {
mCanvas.drawLine(x1, y1, x2, y2, mPaintStroke);
refresh();
return this;
}
//TODO
public Path createPath(float[][] points, boolean close) {
Path path = new Path();
path.moveTo(points[0][0], points[0][1]);
for (int i = 1; i < points.length; i++) {
path.lineTo(points[i][0], points[i][1]);
}
if (close) {
path.close();
}
return path;
}
public PCanvasView path(Path path) {
if (fillOn) mCanvas.drawPath(path, mPaintFill);
if (strokeOn) mCanvas.drawPath(path, mPaintStroke);
refresh();
return this;
}
public PCanvasView strokeDashed(float[] intervals, float phase) {
// Stamp a concave arrow along the line
PathEffect effect = new DashPathEffect(intervals, phase);
mPaintStroke.setPathEffect(effect);
return this;
}
public PCanvasView ellipse(float x1, float y1, float width, float height) {
if (fillOn) mCanvas.drawOval(place(x1, y1, width, height), mPaintFill);
if (strokeOn) mCanvas.drawOval(place(x1, y1, width, height), mPaintStroke);
refresh();
return this;
}
public PCanvasView rect(float x1, float y1, float width, float height) {
if (fillOn) mCanvas.drawRect(place(x1, y1, width, height), mPaintFill);
if (strokeOn) mCanvas.drawRect(place(x1, y1, width, height), mPaintStroke);
refresh();
return this;
}
public PCanvasView rect(float x1, float y1, float width, float height, float rx, float ry) {
if (fillOn) mCanvas.drawRoundRect(place(x1, y1, width, height), rx, ry, mPaintFill);
if (strokeOn) mCanvas.drawRoundRect(place(x1, y1, width, height), rx, ry, mPaintStroke);
refresh();
return this;
}
public PCanvasView arc(float x1, float y1, float x2, float y2, float initAngle, float sweepAngle, boolean center) {
if (fillOn) mCanvas.drawArc(place(x1, y1, x2, y2), initAngle, sweepAngle, center, mPaintFill);
if (strokeOn) mCanvas.drawArc(place(x1, y1, x2, y2), initAngle, sweepAngle, center, mPaintStroke);
refresh();
return this;
}
public PCanvasView text(String text, float x, float y) {
if (fillOn) mCanvas.drawText(text, x, y, mPaintFill);
if (strokeOn) mCanvas.drawText(text, x, y, mPaintStroke);
refresh();
return this;
}
public PCanvasView text(String text,Path path, float initOffset, float outOffset) {
if (fillOn) mCanvas.drawTextOnPath(text, path, initOffset, outOffset, mPaintFill);
if (strokeOn) mCanvas.drawTextOnPath(text, path, initOffset, outOffset, mPaintStroke);
refresh();
return this;
}
public Bitmap loadImage(String imagePath) {
return Image.loadBitmap(ProjectManager.getInstance().getCurrentProject().getStoragePath() + File.separator + imagePath);
}
public PCanvasView image(Bitmap bmp, int x, int y) {
//if (fillOn)
mCanvas.drawBitmap(bmp, x, y, mPaintBackground);
//if (strokeOn) mCanvas.drawBitmap(bmp, x, y, mPaintStroke);
refresh();
return this;
}
public PCanvasView image(Bitmap bmp, int x, int y, int w, int h) {
Rect rectSrc = new Rect(0, 0, bmp.getWidth(), bmp.getHeight());
RectF rectDst = new RectF(x, y, x + w, y + h);
//if (fillOn)
//if (strokeOn)
mCanvas.drawBitmap(bmp, rectSrc, rectDst, mPaintStroke);
refresh();
return this;
}
public void clear() {
mCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
refresh();
}
public PCanvasView fill(int r, int g, int b, int alpha) {
mPaintFill.setStyle(Paint.Style.FILL);
mPaintFill.setARGB(alpha, r, g, b);
fillOn = true;
return this;
}
public PCanvasView fill(int r, int g, int b) {
fill(r, g, b, 255);
fillOn = true;
return this;
}
public void noFill() {
fillOn = false;
}
public PCanvasView stroke(int r, int g, int b, int alpha) {
mPaintStroke.setStyle(Paint.Style.STROKE);
mPaintStroke.setARGB(alpha, r, g, b);
strokeOn = true;
return this;
}
public PCanvasView stroke(int r, int g, int b) {
stroke(r, g, b, 255);
strokeOn = true;
return this;
}
public void noStroke() {
strokeOn = false;
}
public PCanvasView strokeWidth(float w) {
mPaintStroke.setStrokeWidth(w);
return this;
}
public PCanvasView strokeCap(Paint.Cap cap) {
mPaintStroke.setStrokeCap(cap);
return this;
}
public PCanvasView font(Typeface typeface) {
mPaintFill.setTypeface(typeface);
mPaintStroke.setTypeface(typeface);
return this;
}
public PCanvasView textSize(int size) {
mPaintFill.setTextSize(size);
mPaintStroke.setTextSize(size);
return this;
}
public PCanvasView antiAlias(boolean b) {
mPaintFill.setAntiAlias(b);
mPaintStroke.setAntiAlias(b);
return this;
}
public PCanvasView shadowFill(int x, int y, float radius, String colorHex) {
int c = Color.parseColor(colorHex);
mPaintFill.setShadowLayer(radius, x, y, c);
return this;
}
public PCanvasView shadowStroke(int x, int y, float radius, String colorHex) {
int c = Color.parseColor(colorHex);
mPaintStroke.setShadowLayer(radius, x, y, c);
return this;
}
public PCanvasView filter(PorterDuff.Mode mode) {
mPaintFill.setXfermode(new PorterDuffXfermode(mode));
mPaintStroke.setXfermode(new PorterDuffXfermode(mode));
return this;
}
public PCanvasView save() {
mCanvas.save();
return this;
}
public PCanvasView rotate(float degrees) {
mCanvas.rotate(degrees);
return this;
}
public PCanvasView translate(float x, float y) {
mCanvas.translate(x, y);
return this;
}
public PCanvasView skew(float x, float y) {
mCanvas.skew(x, y);
return this;
}
public PCanvasView scale(float x, float y) {
mCanvas.scale(x, y);
return this;
}
public PCanvasView restore() {
mCanvas.restore();
return this;
}
public int newLayer() {
//create a new bitmap
Layer layer = createNewLayer();
layer.visible = true;
mLayerFifo.add(++currentLayer, layer);
mCurrentBmp = layer.bmp;
mCanvas.setBitmap(mCurrentBmp);
return currentLayer;
}
public void deleteLayer(int pos) {
mLayerFifo.remove(pos);
}
public PCanvasView setLayer(int pos, boolean hideAll) {
//all layers off
if (hideAll) {
for (Layer layer : mLayerFifo) {
layer.visible = false;
}
;
}
//desired layer on
Layer layer = mLayerFifo.get(pos);
layer.visible = true;
mCurrentBmp = layer.bmp;
mCanvas.setBitmap(mCurrentBmp);
refresh();
return this;
}
public PCanvasView enableLayer(int pos, boolean b) {
Layer layer = mLayerFifo.get(pos);
layer.visible = b;
mCurrentBmp = layer.bmp;
refresh();
return this;
}
public PCanvasView mode(boolean mode) {
mModeCorner = mode;
return this;
}
public Shader createBitmapShader(Bitmap bitmap, TileMode mode) {
BitmapShader shader = new BitmapShader(bitmap, mode, mode);
return shader;
}
public Shader linearShader(float x1, float y1, float x2, float y2, String c1, String c2, TileMode mode) {
Shader shader = new LinearGradient(x1, y1, x2, y2, Color.parseColor(c1), Color.parseColor(c2), mode);
return shader;
}
public Shader linearShader(float x1, float y1, float x2, float y2, String[] colorsStr, float[] positions, TileMode mode) {
int colors[] = new int[colorsStr.length];
for (int i = 0; i < colors.length; i++) {
colors[i] = Color.parseColor(colorsStr[i]);
}
Shader shader = new LinearGradient(x1, y1, x2, y2, colors, positions, mode);
return shader;
}
public Shader sweepShader(int x, int y, String c1, String c2) {
Shader shader = new SweepGradient(x, y, Color.parseColor(c1), Color.parseColor(c2));
return shader;
}
public Shader composeShader(Shader s1, Shader s2, PorterDuff.Mode mode) {
Shader shader = new ComposeShader(s1, s2, mode);
return shader;
}
public void setShader(Shader shader, TileMode mode) {
mPaintFill.setAntiAlias(true);
mPaintFill.setShader(shader);
}
private RectF place(float x, float y, float width, float height) {
RectF rectf = null;
if (mModeCorner) {
rectf = new RectF(x, y, x + width, y + height);
} else {
rectf = new RectF(x - width / 2, y - height / 2, x + width / 2, y + height / 2);
}
return rectf;
}
private Layer createNewLayer() {
MLog.d(TAG, "createNewLayer of " + mWidth + " " + mHeight);
Bitmap.Config conf = Bitmap.Config.ARGB_8888;
Bitmap _bmp = Bitmap.createBitmap(mWidth, mHeight, conf);
Layer layer = new Layer(_bmp);
return layer;
}
class Layer {
public boolean visible = false;
Bitmap bmp;
Layer(Bitmap bmp) {
this.bmp = bmp;
}
}
}