LatinIME/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java

297 lines
12 KiB
Java
Raw Normal View History

/*
* 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.inputmethod.keyboard.internal;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.SystemClock;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.ResizableIntArray;
final class GesturePreviewTrail {
private static final int DEFAULT_CAPACITY = GestureStrokeWithPreviewPoints.PREVIEW_CAPACITY;
private final ResizableIntArray mXCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
private final ResizableIntArray mYCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
private final ResizableIntArray mEventTimes = new ResizableIntArray(DEFAULT_CAPACITY);
private int mCurrentStrokeId = -1;
// The wall time of the zero value in {@link #mEventTimes}
private long mCurrentTimeBase;
private int mTrailStartIndex;
static final class Params {
public final int mTrailColor;
public final float mTrailStartWidth;
public final float mTrailEndWidth;
public final int mFadeoutStartDelay;
public final int mFadeoutDuration;
public final int mUpdateInterval;
public final int mTrailLingerDuration;
public Params(final TypedArray keyboardViewAttr) {
mTrailColor = keyboardViewAttr.getColor(
R.styleable.KeyboardView_gesturePreviewTrailColor, 0);
mTrailStartWidth = keyboardViewAttr.getDimension(
R.styleable.KeyboardView_gesturePreviewTrailStartWidth, 0.0f);
mTrailEndWidth = keyboardViewAttr.getDimension(
R.styleable.KeyboardView_gesturePreviewTrailEndWidth, 0.0f);
mFadeoutStartDelay = keyboardViewAttr.getInt(
R.styleable.KeyboardView_gesturePreviewTrailFadeoutStartDelay, 0);
mFadeoutDuration = keyboardViewAttr.getInt(
R.styleable.KeyboardView_gesturePreviewTrailFadeoutDuration, 0);
mTrailLingerDuration = mFadeoutStartDelay + mFadeoutDuration;
mUpdateInterval = keyboardViewAttr.getInt(
R.styleable.KeyboardView_gesturePreviewTrailUpdateInterval, 0);
}
}
// Use this value as imaginary zero because x-coordinates may be zero.
private static final int DOWN_EVENT_MARKER = -128;
private static int markAsDownEvent(final int xCoord) {
return DOWN_EVENT_MARKER - xCoord;
}
private static boolean isDownEventXCoord(final int xCoordOrMark) {
return xCoordOrMark <= DOWN_EVENT_MARKER;
}
private static int getXCoordValue(final int xCoordOrMark) {
return isDownEventXCoord(xCoordOrMark)
? DOWN_EVENT_MARKER - xCoordOrMark : xCoordOrMark;
}
public void addStroke(final GestureStrokeWithPreviewPoints stroke, final long downTime) {
final int trailSize = mEventTimes.getLength();
stroke.appendPreviewStroke(mEventTimes, mXCoordinates, mYCoordinates);
if (mEventTimes.getLength() == trailSize) {
return;
}
final int[] eventTimes = mEventTimes.getPrimitiveArray();
final int strokeId = stroke.getGestureStrokeId();
if (strokeId != mCurrentStrokeId) {
final int elapsedTime = (int)(downTime - mCurrentTimeBase);
for (int i = mTrailStartIndex; i < trailSize; i++) {
// Decay the previous strokes' event times.
eventTimes[i] -= elapsedTime;
}
final int[] xCoords = mXCoordinates.getPrimitiveArray();
final int downIndex = trailSize;
xCoords[downIndex] = markAsDownEvent(xCoords[downIndex]);
mCurrentTimeBase = downTime - eventTimes[downIndex];
mCurrentStrokeId = strokeId;
}
}
private static int getAlpha(final int elapsedTime, final Params params) {
if (elapsedTime < params.mFadeoutStartDelay) {
return Constants.Color.ALPHA_OPAQUE;
}
final int decreasingAlpha = Constants.Color.ALPHA_OPAQUE
* (elapsedTime - params.mFadeoutStartDelay)
/ params.mFadeoutDuration;
return Constants.Color.ALPHA_OPAQUE - decreasingAlpha;
}
private static float getWidth(final int elapsedTime, final Params params) {
return Math.max((params.mTrailLingerDuration - elapsedTime)
* (params.mTrailStartWidth - params.mTrailEndWidth)
/ params.mTrailLingerDuration, 0.0f);
}
static final class WorkingSet {
// Input
// Previous point (P1) coordinates and trail radius.
public float p1x, p1y;
public float r1;
// Current point (P2) coordinates and trail radius.
public float p2x, p2y;
public float r2;
// Closing point of arc at P1.
private float p1ax, p1ay;
// Opening point of arc at P1.
private float p1bx, p1by;
// Opening point of arc at P2.
private float p2ax, p2ay;
// Closing point of arc at P2.
private float p2bx, p2by;
// Start angle of the trail arcs.
private float angle;
// Sweep angle of the trail arc at P1.
private float a1;
private final RectF arc1 = new RectF();
// Sweep angle of the trail arc at P2.
private float a2;
private final RectF arc2 = new RectF();
private static final float RADIAN_TO_DEGREE = (float)(180.0d / Math.PI);
private static final float RIGHT_ANGLE = (float)(Math.PI / 2.0d);
public boolean calculatePathPoints() {
final float dx = p2x - p1x;
final float dy = p2y - p1y;
// Distance of the points.
final double l = Math.hypot(dx, dy);
if (Double.compare(0.0d, l) == 0) {
return false;
}
// Angle of the line p1-p2
final float a = (float)Math.atan2(dy, dx);
// Difference of trail cap radius.
final float dr = r2 - r1;
// Variation of angle at trail cap.
final float ar = (float)Math.asin(dr / l);
// The start angle of trail cap arc at P1.
final float aa = a - (RIGHT_ANGLE + ar);
// The end angle of trail cap arc at P2.
final float ab = a + (RIGHT_ANGLE + ar);
final float cosa = (float)Math.cos(aa);
final float sina = (float)Math.sin(aa);
final float cosb = (float)Math.cos(ab);
final float sinb = (float)Math.sin(ab);
p1ax = p1x + r1 * cosa;
p1ay = p1y + r1 * sina;
p1bx = p1x + r1 * cosb;
p1by = p1y + r1 * sinb;
p2ax = p2x + r2 * cosa;
p2ay = p2y + r2 * sina;
p2bx = p2x + r2 * cosb;
p2by = p2y + r2 * sinb;
angle = aa * RADIAN_TO_DEGREE;
final float ar2degree = ar * 2.0f * RADIAN_TO_DEGREE;
a1 = -180.0f + ar2degree;
a2 = 180.0f + ar2degree;
arc1.set(p1x, p1y, p1x, p1y);
arc1.inset(-r1, -r1);
arc2.set(p2x, p2y, p2x, p2y);
arc2.inset(-r2, -r2);
return true;
}
public void createPath(final Path path) {
path.rewind();
// Trail cap at P1.
path.moveTo(p1x, p1y);
path.arcTo(arc1, angle, a1);
// Trail cap at P2.
path.moveTo(p2x, p2y);
path.arcTo(arc2, angle, a2);
// Two trapezoids connecting P1 and P2.
path.moveTo(p1ax, p1ay);
path.lineTo(p1x, p1y);
path.lineTo(p1bx, p1by);
path.lineTo(p2bx, p2by);
path.lineTo(p2x, p2y);
path.lineTo(p2ax, p2ay);
path.close();
}
}
private final WorkingSet mWorkingSet = new WorkingSet();
private final Path mPath = new Path();
/**
* Draw gesture preview trail
* @param canvas The canvas to draw the gesture preview trail
* @param paint The paint object to be used to draw the gesture preview trail
* @param outBoundsRect the bounding box of this gesture trail drawing
* @param params The drawing parameters of gesture preview trail
* @return true if some gesture preview trails remain to be drawn
*/
public boolean drawGestureTrail(final Canvas canvas, final Paint paint,
final Rect outBoundsRect, final Params params) {
final int trailSize = mEventTimes.getLength();
if (trailSize == 0) {
return false;
}
final int[] eventTimes = mEventTimes.getPrimitiveArray();
final int[] xCoords = mXCoordinates.getPrimitiveArray();
final int[] yCoords = mYCoordinates.getPrimitiveArray();
final int sinceDown = (int)(SystemClock.uptimeMillis() - mCurrentTimeBase);
int startIndex;
for (startIndex = mTrailStartIndex; startIndex < trailSize; startIndex++) {
final int elapsedTime = sinceDown - eventTimes[startIndex];
// Skip too old trail points.
if (elapsedTime < params.mTrailLingerDuration) {
break;
}
}
mTrailStartIndex = startIndex;
if (startIndex < trailSize) {
paint.setColor(params.mTrailColor);
paint.setStyle(Paint.Style.FILL);
final Path path = mPath;
final WorkingSet w = mWorkingSet;
w.p1x = getXCoordValue(xCoords[startIndex]);
w.p1y = yCoords[startIndex];
int lastTime = sinceDown - eventTimes[startIndex];
float maxWidth = getWidth(lastTime, params);
w.r1 = maxWidth / 2.0f;
// Initialize bounds rectangle.
outBoundsRect.set((int)w.p1x, (int)w.p1y, (int)w.p1x, (int)w.p1y);
for (int i = startIndex + 1; i < trailSize - 1; i++) {
final int elapsedTime = sinceDown - eventTimes[i];
w.p2x = getXCoordValue(xCoords[i]);
w.p2y = yCoords[i];
// Draw trail line only when the current point isn't a down point.
if (!isDownEventXCoord(xCoords[i])) {
final int alpha = getAlpha(elapsedTime, params);
paint.setAlpha(alpha);
final float width = getWidth(elapsedTime, params);
w.r2 = width / 2.0f;
if (w.calculatePathPoints()) {
w.createPath(path);
canvas.drawPath(path, paint);
outBoundsRect.union((int)w.p2x, (int)w.p2y);
}
// Take union for the bounds.
maxWidth = Math.max(maxWidth, width);
}
w.p1x = w.p2x;
w.p1y = w.p2y;
w.r1 = w.r2;
lastTime = elapsedTime;
}
// Take care of trail line width.
final int inset = -((int)maxWidth + 1);
outBoundsRect.inset(inset, inset);
}
final int newSize = trailSize - startIndex;
if (newSize < startIndex) {
mTrailStartIndex = 0;
if (newSize > 0) {
System.arraycopy(eventTimes, startIndex, eventTimes, 0, newSize);
System.arraycopy(xCoords, startIndex, xCoords, 0, newSize);
System.arraycopy(yCoords, startIndex, yCoords, 0, newSize);
}
mEventTimes.setLength(newSize);
mXCoordinates.setLength(newSize);
mYCoordinates.setLength(newSize);
}
return newSize > 0;
}
}