/**
* PhotoSorterView.java
*
* (c) Luke Hutchison (luke.hutch@mit.edu)
*
* TODO: Add OpenGL acceleration.
*
* --
*
* Released under the MIT license (but please notify me if you use this code, so that I can give your project credit at
* http://code.google.com/p/android-multitouch-controller ).
*
* MIT license: http://www.opensource.org/licenses/MIT
*
* 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 eoc.studio.voicecard.manufacture;
import java.util.ArrayList;
import eoc.studio.voicecard.R;
import eoc.studio.voicecard.manufacture.MultiTouchController.MultiTouchObjectCanvas;
import eoc.studio.voicecard.manufacture.MultiTouchController.PointInfo;
import eoc.studio.voicecard.manufacture.MultiTouchController.PositionAndScale;
import android.R.integer;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
public class PhotoSortrView extends View implements MultiTouchObjectCanvas<PhotoSortrView.Img>
{
private ArrayList<Integer> mImagesList = new ArrayList<Integer>();
private ArrayList<Img> mImages = new ArrayList<Img>();
private MultiTouchController<Img> multiTouchController = new MultiTouchController<Img>(this);
private PointInfo currTouchPoint = new PointInfo();
private boolean mShowDebugInfo = true;
private static final int UI_MODE_ROTATE = 1, UI_MODE_ANISOTROPIC_SCALE = 2;
private int mUIMode = UI_MODE_ROTATE;
private Paint mLinePaintTouchPointCircle = new Paint();
public PhotoSortrView(Context context)
{
this(context, null);
}
public PhotoSortrView(Context context, AttributeSet attrs)
{
this(context, attrs, 0);
}
public PhotoSortrView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
init(context);
}
public void init(Context context)
{
Resources res = context.getResources();
for (int i = 0; i < mImagesList.size(); i++)
mImages.add(new Img(mImagesList.get(i), res));
mLinePaintTouchPointCircle.setColor(Color.YELLOW);
mLinePaintTouchPointCircle.setStrokeWidth(5);
mLinePaintTouchPointCircle.setStyle(Style.STROKE);
mLinePaintTouchPointCircle.setAntiAlias(true);
setBackgroundColor(Color.TRANSPARENT);
}
public void loadOneImgage(Context context, int imageId, float cx, float cy, float sx, float sy)
{
Resources res = context.getResources();
mImagesList.add(imageId);
int size = mImagesList.size();
Log.e("PhotoSortrView", "size is " + size);
if (size > 0)
{
mImages.add(new Img(mImagesList.get(size - 1), res));
// load the last image in the list
mImages.get(size - 1).loadWithPosition(res, cx, cy, sx, sy);
}
}
/** Called by activity's onResume() method to load the images */
public void loadImages(Context context)
{
Resources res = context.getResources();
int n = mImages.size();
for (int i = 0; i < n; i++)
mImages.get(i).load(res);
}
/**
* Called by activity's onPause() method to free memory used for loading the
* images
*/
public void unloadImages()
{
int n = mImages.size();
for (int i = 0; i < n; i++)
mImages.get(i).unload();
}
// ---------------------------------------------------------------------------------------------------
@Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
int n = mImages.size();
for (int i = 0; i < n; i++)
mImages.get(i).draw(canvas);
if (mShowDebugInfo) drawMultitouchDebugMarks(canvas);
}
// ---------------------------------------------------------------------------------------------------
public void trackballClicked()
{
mUIMode = (mUIMode + 1) % 3;
invalidate();
}
private void drawMultitouchDebugMarks(Canvas canvas)
{
if (currTouchPoint.isDown())
{
float[] xs = currTouchPoint.getXs();
float[] ys = currTouchPoint.getYs();
float[] pressures = currTouchPoint.getPressures();
int numPoints = Math.min(currTouchPoint.getNumTouchPoints(), 2);
for (int i = 0; i < numPoints; i++)
canvas.drawCircle(xs[i], ys[i], 50 + pressures[i] * 80, mLinePaintTouchPointCircle);
if (numPoints == 2)
canvas.drawLine(xs[0], ys[0], xs[1], ys[1], mLinePaintTouchPointCircle);
}
}
// ---------------------------------------------------------------------------------------------------
/** Pass touch events to the MT controller */
@Override
public boolean onTouchEvent(MotionEvent event)
{
return multiTouchController.onTouchEvent(event);
}
/**
* Get the image that is under the single-touch point, or return null
* (canceling the drag op) if none
*/
public Img getDraggableObjectAtPoint(PointInfo pt)
{
float x = pt.getX(), y = pt.getY();
int n = mImages.size();
for (int i = n - 1; i >= 0; i--)
{
Img im = mImages.get(i);
if (im.containsPoint(x, y)) return im;
}
return null;
}
/**
* Select an object for dragging. Called whenever an object is found to be
* under the point (non-null is returned by getDraggableObjectAtPoint()) and
* a drag operation is starting. Called with null when drag op ends.
*/
public void selectObject(Img img, PointInfo touchPoint)
{
currTouchPoint.set(touchPoint);
if (img != null)
{
// Move image to the top of the stack when selected
mImages.remove(img);
mImages.add(img);
}
else
{
// Called with img == null when drag stops.
}
invalidate();
}
/**
* Get the current position and scale of the selected image. Called whenever
* a drag starts or is reset.
*/
public void getPositionAndScale(Img img, PositionAndScale objPosAndScaleOut)
{
// FIXME affine-izem (and fix the fact that the anisotropic_scale part
// requires averaging the two scale factors)
objPosAndScaleOut.set(img.getCenterX(), img.getCenterY(),
(mUIMode & UI_MODE_ANISOTROPIC_SCALE) == 0,
(img.getScaleX() + img.getScaleY()) / 2,
(mUIMode & UI_MODE_ANISOTROPIC_SCALE) != 0, img.getScaleX(), img.getScaleY(),
(mUIMode & UI_MODE_ROTATE) != 0, img.getAngle());
}
/** Set the position and scale of the dragged/stretched image. */
public boolean setPositionAndScale(Img img, PositionAndScale newImgPosAndScale,
PointInfo touchPoint)
{
currTouchPoint.set(touchPoint);
boolean ok = img.setPos(newImgPosAndScale);
if (ok) invalidate();
return ok;
}
// ----------------------------------------------------------------------------------------------
class Img
{
private int resId;
private Drawable drawable;
private boolean firstLoad;
private int width, height, displayWidth, displayHeight;
private float centerX, centerY, scaleX, scaleY, angle;
private float minX, maxX, minY, maxY;
private static final float SCREEN_MARGIN = 100;
public Img(int resId, Resources res)
{
this.resId = resId;
this.firstLoad = true;
getMetrics(res);
}
private void getMetrics(Resources res)
{
DisplayMetrics metrics = res.getDisplayMetrics();
// The DisplayMetrics don't seem to always be updated on screen
// rotate, so we hard code a portrait
// screen orientation for the non-rotated screen here...
// this.displayWidth = metrics.widthPixels;
// this.displayHeight = metrics.heightPixels;
this.displayWidth = res.getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE ? Math
.max(metrics.widthPixels, metrics.heightPixels) : Math.min(metrics.widthPixels,
metrics.heightPixels);
this.displayHeight = res.getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE ? Math
.min(metrics.widthPixels, metrics.heightPixels) : Math.max(metrics.widthPixels,
metrics.heightPixels);
}
public void loadWithPosition(Resources res, float cx, float cy, float sx, float sy)
{
getMetrics(res);
this.drawable = res.getDrawable(resId);
this.width = drawable.getIntrinsicWidth();
this.height = drawable.getIntrinsicHeight();
/*@bruce mark the original code
if (this.maxX < SCREEN_MARGIN)
cx = SCREEN_MARGIN;
else if (this.minX > displayWidth - SCREEN_MARGIN) cx = displayWidth - SCREEN_MARGIN;
if (this.maxY > SCREEN_MARGIN)
cy = SCREEN_MARGIN;
else if (this.minY > displayHeight - SCREEN_MARGIN) cy = displayHeight - SCREEN_MARGIN;
*/
setPos(cx, cy, sx, sy, 0.0f);
}
/** Called by activity's onResume() method to load the images */
public void load(Resources res)
{
getMetrics(res);
this.drawable = res.getDrawable(resId);
this.width = drawable.getIntrinsicWidth();
this.height = drawable.getIntrinsicHeight();
float cx, cy, sx, sy;
if (firstLoad)
{
cx = 100;
cy = 100;
float sc = (float) 1.0;
/*
* cx = SCREEN_MARGIN + (float) (Math.random() * (displayWidth -
* 2 * SCREEN_MARGIN)); cy = SCREEN_MARGIN + (float)
* (Math.random() * (displayHeight - 2 * SCREEN_MARGIN));
*/
// float sc = (float) (Math.max(displayWidth, displayHeight) /
// (float) Math.max(width, height) * Math.random() * 0.3 + 0.2);
sx = sy = sc;
firstLoad = false;
}
else
{
// Reuse position and scale information if it is available
// FIXME this doesn't actually work because the whole activity
// is torn down and re-created on rotate
cx = this.centerX;
cy = this.centerY;
sx = this.scaleX;
sy = this.scaleY;
// Make sure the image is not off the screen after a screen
// rotation
if (this.maxX < SCREEN_MARGIN)
cx = SCREEN_MARGIN;
else if (this.minX > displayWidth - SCREEN_MARGIN)
cx = displayWidth - SCREEN_MARGIN;
if (this.maxY > SCREEN_MARGIN)
cy = SCREEN_MARGIN;
else if (this.minY > displayHeight - SCREEN_MARGIN)
cy = displayHeight - SCREEN_MARGIN;
}
setPos(cx, cy, sx, sy, 0.0f);
}
/**
* Called by activity's onPause() method to free memory used for loading
* the images
*/
public void unload()
{
this.drawable = null;
}
/** Set the position and scale of an image in screen coordinates */
public boolean setPos(PositionAndScale newImgPosAndScale)
{
return setPos(newImgPosAndScale.getXOff(), newImgPosAndScale.getYOff(),
(mUIMode & UI_MODE_ANISOTROPIC_SCALE) != 0 ? newImgPosAndScale.getScaleX()
: newImgPosAndScale.getScale(),
(mUIMode & UI_MODE_ANISOTROPIC_SCALE) != 0 ? newImgPosAndScale.getScaleY()
: newImgPosAndScale.getScale(), newImgPosAndScale.getAngle());
// FIXME: anisotropic scaling jumps when axis-snapping
// FIXME: affine-ize
// return setPos(newImgPosAndScale.getXOff(),
// newImgPosAndScale.getYOff(),
// newImgPosAndScale.getScaleAnisotropicX(),
// newImgPosAndScale.getScaleAnisotropicY(), 0.0f);
}
/** Set the position and scale of an image in screen coordinates */
private boolean setPos(float centerX, float centerY, float scaleX, float scaleY, float angle)
{
float ws = (width / 2) * scaleX, hs = (height / 2) * scaleY;
float newMinX = centerX - ws, newMinY = centerY - hs, newMaxX = centerX + ws, newMaxY = centerY
+ hs;
if (newMinX > displayWidth - SCREEN_MARGIN || newMaxX < SCREEN_MARGIN
|| newMinY > displayHeight - SCREEN_MARGIN || newMaxY < SCREEN_MARGIN)
return false;
this.centerX = centerX;
this.centerY = centerY;
this.scaleX = scaleX;
this.scaleY = scaleY;
this.angle = angle;
this.minX = newMinX;
this.minY = newMinY;
this.maxX = newMaxX;
this.maxY = newMaxY;
return true;
}
/** Return whether or not the given screen coords are inside this image */
public boolean containsPoint(float scrnX, float scrnY)
{
// FIXME: need to correctly account for image rotation
return (scrnX >= minX && scrnX <= maxX && scrnY >= minY && scrnY <= maxY);
}
public void draw(Canvas canvas)
{
canvas.save();
float dx = (maxX + minX) / 2;
float dy = (maxY + minY) / 2;
drawable.setBounds((int) minX, (int) minY, (int) maxX, (int) maxY);
canvas.translate(dx, dy);
canvas.rotate(angle * 180.0f / (float) Math.PI);
canvas.translate(-dx, -dy);
drawable.draw(canvas);
canvas.restore();
}
public Drawable getDrawable()
{
return drawable;
}
public int getWidth()
{
return width;
}
public int getHeight()
{
return height;
}
public float getCenterX()
{
return centerX;
}
public float getCenterY()
{
return centerY;
}
public float getScaleX()
{
return scaleX;
}
public float getScaleY()
{
return scaleY;
}
public float getAngle()
{
return angle;
}
// FIXME: these need to be updated for rotation
public float getMinX()
{
return minX;
}
public float getMaxX()
{
return maxX;
}
public float getMinY()
{
return minY;
}
public float getMaxY()
{
return maxY;
}
}
}