blob: 8bcec6b7b6553c8c8b4809f2cd6fa6f29453e603 [file] [log] [blame]
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.dreams.phototable;
import android.content.Context;
import android.content.res.Resources;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
/**
* Touch listener that implements phototable interactions.
*/
public class PhotoTouchListener implements View.OnTouchListener {
private static final String TAG = "PhotoTouchListener";
private static final boolean DEBUG = false;
private static final int INVALID_POINTER = -1;
private static final int MAX_POINTER_COUNT = 10;
private final int mTouchSlop;
private final int mTapTimeout;
private final PhotoTable mTable;
private final float mBeta;
private final boolean mEnableFling;
private final boolean mManualImageRotation;
private long mLastEventTime;
private float mLastTouchX;
private float mLastTouchY;
private float mInitialTouchX;
private float mInitialTouchY;
private float mInitialTouchA;
private long mInitialTouchTime;
private float mInitialTargetX;
private float mInitialTargetY;
private float mInitialTargetA;
private float mDX;
private float mDY;
private int mA = INVALID_POINTER;
private int mB = INVALID_POINTER;
private float[] pts = new float[MAX_POINTER_COUNT];
public PhotoTouchListener(Context context, PhotoTable table) {
mTable = table;
final ViewConfiguration configuration = ViewConfiguration.get(context);
mTouchSlop = configuration.getScaledTouchSlop();
mTapTimeout = ViewConfiguration.getTapTimeout();
final Resources resources = context.getResources();
mBeta = resources.getInteger(R.integer.table_damping) / 1000000f;
mEnableFling = resources.getBoolean(R.bool.enable_fling);
mManualImageRotation = resources.getBoolean(R.bool.enable_manual_image_rotation);
}
/** Get angle defined by first two touches, in degrees */
private float getAngle(View target, MotionEvent ev) {
float alpha = 0f;
int a = ev.findPointerIndex(mA);
int b = ev.findPointerIndex(mB);
if (a >=0 && b >=0) {
alpha = (float) (Math.atan2(pts[2*a + 1] - pts[2*b + 1],
pts[2*a] - pts[2*b]) *
180f / Math.PI);
}
return alpha;
}
private void resetTouch(View target) {
mInitialTouchX = -1;
mInitialTouchY = -1;
mInitialTouchA = 0f;
mInitialTargetX = (float) target.getX();
mInitialTargetY = (float) target.getY();
mInitialTargetA = (float) target.getRotation();
}
public void onFling(View target, float dX, float dY) {
if (!mEnableFling) {
return;
}
log("fling " + dX + ", " + dY);
// convert to pixel per frame
dX /= 60f;
dY /= 60f;
// starting position compionents in global corrdinate frame
final float x0 = pts[0];
final float y0 = pts[1];
// velocity
final float v = (float) Math.hypot(dX, dY);
if (v == 0f) {
return;
}
// number of steps to come to a stop
final float n = (float) Math.max(1.0, (- Math.log(v) / Math.log(mBeta)));
// distance travelled before stopping
final float s = (float) Math.max(0.0, (v * (1f - Math.pow(mBeta, n)) / (1f - mBeta)));
// ending posiiton after stopping
final float x1 = x0 + s * dX / v;
final float y1 = y0 + s * dY / v;
mTable.fling(target, x1 - x0, y1 - y0, (int) (1000f * n / 60f), false);
}
@Override
public boolean onTouch(View target, MotionEvent ev) {
final int action = ev.getActionMasked();
// compute raw coordinates
for(int i = 0; i < 10 && i < ev.getPointerCount(); i++) {
pts[i*2] = ev.getX(i);
pts[i*2 + 1] = ev.getY(i);
}
target.getMatrix().mapPoints(pts);
switch (action) {
case MotionEvent.ACTION_DOWN:
mTable.moveToTopOfPile(target);
mInitialTouchTime = ev.getEventTime();
mA = ev.getPointerId(ev.getActionIndex());
resetTouch(target);
break;
case MotionEvent.ACTION_POINTER_DOWN:
if (mB == INVALID_POINTER) {
mB = ev.getPointerId(ev.getActionIndex());
mInitialTouchA = getAngle(target, ev);
}
break;
case MotionEvent.ACTION_POINTER_UP:
if (mB == ev.getPointerId(ev.getActionIndex())) {
mB = INVALID_POINTER;
mInitialTargetA = (float) target.getRotation();
}
if (mA == ev.getPointerId(ev.getActionIndex())) {
log("primary went up!");
mA = mB;
resetTouch(target);
mB = INVALID_POINTER;
}
break;
case MotionEvent.ACTION_MOVE: {
if (mA != INVALID_POINTER) {
int idx = ev.findPointerIndex(mA);
float x = pts[2 * idx];
float y = pts[2 * idx + 1];
if (mInitialTouchX == -1 && mInitialTouchY == -1) {
mInitialTouchX = x;
mInitialTouchY = y;
} else {
float dt = (float) (ev.getEventTime() - mLastEventTime) / 1000f;
float tmpDX = (x - mLastTouchX) / dt;
float tmpDY = (y - mLastTouchY) / dt;
if (dt > 0f && (Math.abs(tmpDX) > 5f || Math.abs(tmpDY) > 5f)) {
// work around odd bug with multi-finger flings
mDX = tmpDX;
mDY = tmpDY;
}
log("move " + mDX + ", " + mDY);
mLastEventTime = ev.getEventTime();
mLastTouchX = x;
mLastTouchY = y;
}
if (!mTable.hasSelection()) {
float rotation = target.getRotation();
if (mManualImageRotation && mB != INVALID_POINTER) {
float a = getAngle(target, ev);
rotation = mInitialTargetA + a - mInitialTouchA;
}
mTable.move(target,
mInitialTargetX + x - mInitialTouchX,
mInitialTargetY + y - mInitialTouchY,
rotation);
}
}
}
break;
case MotionEvent.ACTION_UP: {
if (mA != INVALID_POINTER) {
int idx = ev.findPointerIndex(mA);
float x0 = pts[2 * idx];
float y0 = pts[2 * idx + 1];
if (mInitialTouchX == -1 && mInitialTouchY == -1) {
mInitialTouchX = x0;
mInitialTouchY = y0;
}
double distance = Math.hypot(x0 - mInitialTouchX,
y0 - mInitialTouchY);
if (mTable.hasSelection()) {
if (distance < mTouchSlop) {
mTable.clearSelection();
} else {
if ((x0 - mInitialTouchX) > 0f) {
mTable.selectPrevious();
} else {
mTable.selectNext();
}
}
} else if ((ev.getEventTime() - mInitialTouchTime) < mTapTimeout &&
distance < mTouchSlop) {
// tap
mTable.setSelection(target);
} else {
onFling(target, mDX, mDY);
}
mA = INVALID_POINTER;
mB = INVALID_POINTER;
}
}
break;
case MotionEvent.ACTION_CANCEL:
log("action cancel!");
break;
}
return true;
}
private static void log(String message) {
if (DEBUG) {
Log.i(TAG, message);
}
}
}