From c7dc673cf0fb56015826079423ced659b9180feb Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Fri, 14 Sep 2012 18:10:39 +0900 Subject: [PATCH] Use Path to draw gesture preview trail This change also reduces the number of points to use as preview trail. Bug: 7167303 Change-Id: I0cf4908efa44b17b42d4fddd6725238236ac2654 --- .../inputmethod/keyboard/PointerTracker.java | 47 +++--- .../internal/GesturePreviewTrail.java | 135 +++++++++++++++--- .../keyboard/internal/GestureStroke.java | 2 +- ...va => GestureStrokeWithPreviewPoints.java} | 37 ++++- .../keyboard/internal/PreviewPlacerView.java | 2 +- 5 files changed, 178 insertions(+), 45 deletions(-) rename java/src/com/android/inputmethod/keyboard/internal/{GestureStrokeWithPreviewTrail.java => GestureStrokeWithPreviewPoints.java} (65%) diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java index a6439c46a..2bde8d2c5 100644 --- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java +++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java @@ -23,7 +23,7 @@ import android.view.MotionEvent; import com.android.inputmethod.accessibility.AccessibilityUtils; import com.android.inputmethod.keyboard.internal.GestureStroke; -import com.android.inputmethod.keyboard.internal.GestureStrokeWithPreviewTrail; +import com.android.inputmethod.keyboard.internal.GestureStrokeWithPreviewPoints; import com.android.inputmethod.keyboard.internal.PointerTrackerQueue; import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.InputPointers; @@ -208,7 +208,7 @@ public class PointerTracker implements PointerTrackerQueue.Element { private static final KeyboardActionListener EMPTY_LISTENER = new KeyboardActionListener.Adapter(); - private final GestureStrokeWithPreviewTrail mGestureStrokeWithPreviewTrail; + private final GestureStrokeWithPreviewPoints mGestureStrokeWithPreviewPoints; public static void init(boolean hasDistinctMultitouch, boolean needsPhantomSuddenMoveEventHack) { @@ -293,7 +293,7 @@ public class PointerTracker implements PointerTrackerQueue.Element { throw new NullPointerException(); } mPointerId = id; - mGestureStrokeWithPreviewTrail = new GestureStrokeWithPreviewTrail(id); + mGestureStrokeWithPreviewPoints = new GestureStrokeWithPreviewPoints(id); setKeyDetectorInner(handler.getKeyDetector()); mListener = handler.getKeyboardActionListener(); mDrawingProxy = handler.getDrawingProxy(); @@ -392,7 +392,7 @@ public class PointerTracker implements PointerTrackerQueue.Element { private void setKeyDetectorInner(final KeyDetector keyDetector) { mKeyDetector = keyDetector; mKeyboard = keyDetector.getKeyboard(); - mGestureStrokeWithPreviewTrail.setGestureSampleLength(mKeyboard.mMostCommonKeyWidth); + mGestureStrokeWithPreviewPoints.setKeyboardGeometry(mKeyboard.mMostCommonKeyWidth); final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY); if (newKey != mCurrentKey) { if (mDrawingProxy != null) { @@ -502,8 +502,8 @@ public class PointerTracker implements PointerTrackerQueue.Element { mDrawingProxy.invalidateKey(key); } - public GestureStrokeWithPreviewTrail getGestureStrokeWithPreviewTrail() { - return mGestureStrokeWithPreviewTrail; + public GestureStrokeWithPreviewPoints getGestureStrokeWithPreviewPoints() { + return mGestureStrokeWithPreviewPoints; } public int getLastX() { @@ -544,8 +544,8 @@ public class PointerTracker implements PointerTrackerQueue.Element { return (sPointerTrackerQueue == null) ? 1 : sPointerTrackerQueue.size(); } - private void startBatchInput() { - if (sInGesture || !mGestureStrokeWithPreviewTrail.isStartOfAGesture()) { + private void mayStartBatchInput() { + if (sInGesture || !mGestureStrokeWithPreviewPoints.isStartOfAGesture()) { return; } if (DEBUG_LISTENER) { @@ -559,7 +559,7 @@ public class PointerTracker implements PointerTrackerQueue.Element { private void updateBatchInput(final long eventTime) { synchronized (sAggregratedPointers) { - mGestureStrokeWithPreviewTrail.appendIncrementalBatchPoints(sAggregratedPointers); + mGestureStrokeWithPreviewPoints.appendIncrementalBatchPoints(sAggregratedPointers); final int size = sAggregratedPointers.getPointerSize(); if (size > sLastRecognitionPointSize && eventTime > sLastRecognitionTime + MIN_GESTURE_RECOGNITION_TIME) { @@ -575,10 +575,10 @@ public class PointerTracker implements PointerTrackerQueue.Element { mDrawingProxy.showGesturePreviewTrail(this, isOldestTracker); } - private void endBatchInput() { + private void mayEndBatchInput() { synchronized (sAggregratedPointers) { - mGestureStrokeWithPreviewTrail.appendAllBatchPoints(sAggregratedPointers); - mGestureStrokeWithPreviewTrail.reset(); + mGestureStrokeWithPreviewPoints.appendAllBatchPoints(sAggregratedPointers); + mGestureStrokeWithPreviewPoints.reset(); if (getActivePointerTrackerCount() == 1) { if (DEBUG_LISTENER) { Log.d(TAG, "onEndBatchInput: batchPoints=" @@ -601,7 +601,7 @@ public class PointerTracker implements PointerTrackerQueue.Element { final int trackersSize = sTrackers.size(); for (int i = 0; i < trackersSize; ++i) { final PointerTracker tracker = sTrackers.get(i); - tracker.mGestureStrokeWithPreviewTrail.reset(); + tracker.mGestureStrokeWithPreviewPoints.reset(); } sAggregratedPointers.reset(); sLastRecognitionPointSize = 0; @@ -678,18 +678,21 @@ public class PointerTracker implements PointerTrackerQueue.Element { && mKeyboard.mId.isAlphabetKeyboard(); if (isAlphabetKeyboard && !mIsShowingMoreKeysPanel && key != null && Keyboard.isLetterCode(key.mCode)) { - mIsDetectingGesture = true; sGestureFirstDownTime = eventTime; - mGestureStrokeWithPreviewTrail.addPoint(x, y, 0, false /* isHistorical */); + onGestureDownEvent(x, y, eventTime); } } else if (sInGesture && activePointerTrackerCount > 1) { - mIsDetectingGesture = true; - final int elapsedTimeFromFirstDown = (int)(eventTime - sGestureFirstDownTime); - mGestureStrokeWithPreviewTrail.addPoint(x, y, elapsedTimeFromFirstDown, - false /* isHistorical */); + onGestureDownEvent(x, y, eventTime); } } + private void onGestureDownEvent(final int x, final int y, final long eventTime) { + mIsDetectingGesture = true; + final int elapsedTimeFromFirstDown = (int)(eventTime - sGestureFirstDownTime); + mGestureStrokeWithPreviewPoints.addPoint(x, y, elapsedTimeFromFirstDown, + false /* isHistorical */); + } + private void onDownEventInternal(final int x, final int y, final long eventTime) { Key key = onDownKey(x, y, eventTime); // Sliding key is allowed when 1) enabled by configuration, 2) this pointer starts sliding @@ -726,8 +729,8 @@ public class PointerTracker implements PointerTrackerQueue.Element { final boolean isHistorical, final Key key) { final int gestureTime = (int)(eventTime - sGestureFirstDownTime); if (mIsDetectingGesture) { - mGestureStrokeWithPreviewTrail.addPoint(x, y, gestureTime, isHistorical); - startBatchInput(); + mGestureStrokeWithPreviewPoints.addPoint(x, y, gestureTime, isHistorical); + mayStartBatchInput(); if (sInGesture && key != null) { updateBatchInput(eventTime); } @@ -919,7 +922,7 @@ public class PointerTracker implements PointerTrackerQueue.Element { if (currentKey != null) { callListenerOnRelease(currentKey, currentKey.mCode, true); } - endBatchInput(); + mayEndBatchInput(); return; } // This event will be recognized as a regular code input. Clear unused possible batch points diff --git a/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java index 4311fa775..699aaeaef 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java +++ b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java @@ -17,7 +17,9 @@ 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; @@ -25,7 +27,7 @@ import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.ResizableIntArray; final class GesturePreviewTrail { - private static final int DEFAULT_CAPACITY = GestureStrokeWithPreviewTrail.PREVIEW_CAPACITY; + 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); @@ -78,7 +80,7 @@ final class GesturePreviewTrail { ? DOWN_EVENT_MARKER - xCoordOrMark : xCoordOrMark; } - public void addStroke(final GestureStrokeWithPreviewTrail stroke, final long downTime) { + public void addStroke(final GestureStrokeWithPreviewPoints stroke, final long downTime) { final int trailSize = mEventTimes.getLength(); stroke.appendPreviewStroke(mEventTimes, mXCoordinates, mYCoordinates); if (mEventTimes.getLength() == trailSize) { @@ -116,6 +118,99 @@ final class GesturePreviewTrail { / 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; + + // Output + // Closing point of arc at P1. + public float p1ax, p1ay; + // Opening point of arc at P1. + public float p1bx, p1by; + // Opening point of arc at P2. + public float p2ax, p2ay; + // Closing point of arc at P2. + public float p2bx, p2by; + // Start angle of the trail arcs. + public float aa; + // Sweep angle of the trail arc at P1. + public float a1; + public RectF arc1 = new RectF(); + // Sweep angle of the trail arc at P2. + public float a2; + public RectF arc2 = new RectF(); + } + + private static final float RIGHT_ANGLE = (float)(Math.PI / 2.0d); + private static final float RADIAN_TO_DEGREE = (float)(180.0d / Math.PI); + + private static boolean calculatePathPoints(final WorkingSet w) { + final float dx = w.p2x - w.p1x; + final float dy = w.p2y - w.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 = w.r2 - w.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); + w.p1ax = w.p1x + w.r1 * cosa; + w.p1ay = w.p1y + w.r1 * sina; + w.p1bx = w.p1x + w.r1 * cosb; + w.p1by = w.p1y + w.r1 * sinb; + w.p2ax = w.p2x + w.r2 * cosa; + w.p2ay = w.p2y + w.r2 * sina; + w.p2bx = w.p2x + w.r2 * cosb; + w.p2by = w.p2y + w.r2 * sinb; + w.aa = aa * RADIAN_TO_DEGREE; + final float ar2degree = ar * 2.0f * RADIAN_TO_DEGREE; + w.a1 = -180.0f + ar2degree; + w.a2 = 180.0f + ar2degree; + w.arc1.set(w.p1x, w.p1y, w.p1x, w.p1y); + w.arc1.inset(-w.r1, -w.r1); + w.arc2.set(w.p2x, w.p2y, w.p2x, w.p2y); + w.arc2.inset(-w.r2, -w.r2); + return true; + } + + private static void createPath(final Path path, final WorkingSet w) { + path.rewind(); + // Trail cap at P1. + path.moveTo(w.p1x, w.p1y); + path.arcTo(w.arc1, w.aa, w.a1); + // Trail cap at P2. + path.moveTo(w.p2x, w.p2y); + path.arcTo(w.arc2, w.aa, w.a2); + // Two trapezoids connecting P1 and P2. + path.moveTo(w.p1ax, w.p1ay); + path.lineTo(w.p1x, w.p1y); + path.lineTo(w.p1bx, w.p1by); + path.lineTo(w.p2bx, w.p2by); + path.lineTo(w.p2x, w.p2y); + path.lineTo(w.p2ax, w.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 @@ -147,30 +242,38 @@ final class GesturePreviewTrail { if (startIndex < trailSize) { paint.setColor(params.mTrailColor); - paint.setStyle(Paint.Style.STROKE); - paint.setStrokeCap(Paint.Cap.ROUND); - int lastX = getXCoordValue(xCoords[startIndex]); - int lastY = yCoords[startIndex]; - float maxWidth = getWidth(sinceDown - eventTimes[startIndex], params); + 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(lastX, lastY, lastX, lastY); + 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 x = xCoords[i]; - final int y = yCoords[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(x)) { + if (!isDownEventXCoord(xCoords[i])) { final int alpha = getAlpha(elapsedTime, params); paint.setAlpha(alpha); final float width = getWidth(elapsedTime, params); - paint.setStrokeWidth(width); - canvas.drawLine(lastX, lastY, x, y, paint); + w.r2 = width / 2.0f; + if (calculatePathPoints(w)) { + createPath(path, w); + canvas.drawPath(path, paint); + outBoundsRect.union((int)w.p2x, (int)w.p2y); + } // Take union for the bounds. - outBoundsRect.union(x, y); maxWidth = Math.max(maxWidth, width); } - lastX = getXCoordValue(x); - lastY = y; + 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); diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java index 7a8c2409c..093a530d5 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java @@ -44,7 +44,7 @@ public class GestureStroke { mPointerId = pointerId; } - public void setGestureSampleLength(final int keyWidth) { + public void setKeyboardGeometry(final int keyWidth) { // TODO: Find an appropriate base metric for these length. Maybe diagonal length of the key? mMinGestureLength = (int)(keyWidth * MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH); mMinGestureSampleLength = (int)(keyWidth * MIN_GESTURE_SAMPLING_RATIO_TO_KEY_WIDTH); diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewTrail.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java similarity index 65% rename from java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewTrail.java rename to java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java index 6c1a9bc01..ce3914076 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewTrail.java +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java @@ -16,7 +16,7 @@ package com.android.inputmethod.keyboard.internal; import com.android.inputmethod.latin.ResizableIntArray; -public class GestureStrokeWithPreviewTrail extends GestureStroke { +public class GestureStrokeWithPreviewPoints extends GestureStroke { public static final int PREVIEW_CAPACITY = 256; private final ResizableIntArray mPreviewEventTimes = new ResizableIntArray(PREVIEW_CAPACITY); @@ -26,7 +26,14 @@ public class GestureStrokeWithPreviewTrail extends GestureStroke { private int mStrokeId; private int mLastPreviewSize; - public GestureStrokeWithPreviewTrail(final int pointerId) { + private int mMinPreviewSampleLengthSquare; + private int mLastX; + private int mLastY; + + // TODO: Move this to resource. + private static final float MIN_PREVIEW_SAMPLE_LENGTH_RATIO_TO_KEY_WIDTH = 0.1f; + + public GestureStrokeWithPreviewPoints(final int pointerId) { super(pointerId); } @@ -48,12 +55,32 @@ public class GestureStrokeWithPreviewTrail extends GestureStroke { return mPreviewEventTimes.getLength(); } + @Override + public void setKeyboardGeometry(final int keyWidth) { + super.setKeyboardGeometry(keyWidth); + final float sampleLength = keyWidth * MIN_PREVIEW_SAMPLE_LENGTH_RATIO_TO_KEY_WIDTH; + mMinPreviewSampleLengthSquare = (int)(sampleLength * sampleLength); + } + + private boolean needsSampling(final int x, final int y) { + final int dx = x - mLastX; + final int dy = y - mLastY; + final boolean needsSampling = (dx * dx + dy * dy >= mMinPreviewSampleLengthSquare); + if (needsSampling) { + mLastX = x; + mLastY = y; + } + return needsSampling; + } + @Override public void addPoint(final int x, final int y, final int time, final boolean isHistorical) { super.addPoint(x, y, time, isHistorical); - mPreviewEventTimes.add(time); - mPreviewXCoordinates.add(x); - mPreviewYCoordinates.add(y); + if (mPreviewEventTimes.getLength() == 0 || isHistorical || needsSampling(x, y)) { + mPreviewEventTimes.add(time); + mPreviewXCoordinates.add(x); + mPreviewYCoordinates.add(y); + } } public void appendPreviewStroke(final ResizableIntArray eventTimes, diff --git a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java index 915ce1f36..075a9bb0c 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java +++ b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java @@ -205,7 +205,7 @@ public class PreviewPlacerView extends RelativeLayout { mGesturePreviewTrails.put(tracker.mPointerId, trail); } } - trail.addStroke(tracker.getGestureStrokeWithPreviewTrail(), tracker.getDownTime()); + trail.addStroke(tracker.getGestureStrokeWithPreviewPoints(), tracker.getDownTime()); } // TODO: Should narrow the invalidate region.