• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.inputmethod.keyboard.internal;
18 
19 import android.content.res.TypedArray;
20 import android.graphics.Canvas;
21 import android.graphics.Color;
22 import android.graphics.Paint;
23 import android.graphics.Path;
24 import android.graphics.Rect;
25 import android.os.SystemClock;
26 
27 import com.android.inputmethod.latin.Constants;
28 import com.android.inputmethod.latin.R;
29 import com.android.inputmethod.latin.ResizableIntArray;
30 
31 /*
32  * @attr ref R.styleable#MainKeyboardView_gestureTrailFadeoutStartDelay
33  * @attr ref R.styleable#MainKeyboardView_gestureTrailFadeoutDuration
34  * @attr ref R.styleable#MainKeyboardView_gestureTrailUpdateInterval
35  * @attr ref R.styleable#MainKeyboardView_gestureTrailColor
36  * @attr ref R.styleable#MainKeyboardView_gestureTrailWidth
37  */
38 final class GestureTrail {
39     public static final boolean DBG_SHOW_POINTS = false;
40     public static final int POINT_TYPE_SAMPLED = 0;
41     public static final int POINT_TYPE_INTERPOLATED = 1;
42     public static final int POINT_TYPE_COMPROMISED = 2;
43 
44     private static final int DEFAULT_CAPACITY = GestureStrokeWithPreviewPoints.PREVIEW_CAPACITY;
45 
46     // These three {@link ResizableIntArray}s should be synchronized by {@link #mEventTimes}.
47     private final ResizableIntArray mXCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
48     private final ResizableIntArray mYCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
49     private final ResizableIntArray mEventTimes = new ResizableIntArray(DEFAULT_CAPACITY);
50     private final ResizableIntArray mPointTypes = new ResizableIntArray(
51             DBG_SHOW_POINTS ? DEFAULT_CAPACITY : 0);
52     private int mCurrentStrokeId = -1;
53     // The wall time of the zero value in {@link #mEventTimes}
54     private long mCurrentTimeBase;
55     private int mTrailStartIndex;
56     private int mLastInterpolatedDrawIndex;
57 
58     static final class Params {
59         public final int mTrailColor;
60         public final float mTrailStartWidth;
61         public final float mTrailEndWidth;
62         public final float mTrailBodyRatio;
63         public boolean mTrailShadowEnabled;
64         public final float mTrailShadowRatio;
65         public final int mFadeoutStartDelay;
66         public final int mFadeoutDuration;
67         public final int mUpdateInterval;
68 
69         public final int mTrailLingerDuration;
70 
Params(final TypedArray mainKeyboardViewAttr)71         public Params(final TypedArray mainKeyboardViewAttr) {
72             mTrailColor = mainKeyboardViewAttr.getColor(
73                     R.styleable.MainKeyboardView_gestureTrailColor, 0);
74             mTrailStartWidth = mainKeyboardViewAttr.getDimension(
75                     R.styleable.MainKeyboardView_gestureTrailStartWidth, 0.0f);
76             mTrailEndWidth = mainKeyboardViewAttr.getDimension(
77                     R.styleable.MainKeyboardView_gestureTrailEndWidth, 0.0f);
78             final int PERCENTAGE_INT = 100;
79             mTrailBodyRatio = (float)mainKeyboardViewAttr.getInt(
80                     R.styleable.MainKeyboardView_gestureTrailBodyRatio, PERCENTAGE_INT)
81                     / (float)PERCENTAGE_INT;
82             final int trailShadowRatioInt = mainKeyboardViewAttr.getInt(
83                     R.styleable.MainKeyboardView_gestureTrailShadowRatio, 0);
84             mTrailShadowEnabled = (trailShadowRatioInt > 0);
85             mTrailShadowRatio = (float)trailShadowRatioInt / (float)PERCENTAGE_INT;
86             mFadeoutStartDelay = DBG_SHOW_POINTS ? 2000 : mainKeyboardViewAttr.getInt(
87                     R.styleable.MainKeyboardView_gestureTrailFadeoutStartDelay, 0);
88             mFadeoutDuration = DBG_SHOW_POINTS ? 200 : mainKeyboardViewAttr.getInt(
89                     R.styleable.MainKeyboardView_gestureTrailFadeoutDuration, 0);
90             mTrailLingerDuration = mFadeoutStartDelay + mFadeoutDuration;
91             mUpdateInterval = mainKeyboardViewAttr.getInt(
92                     R.styleable.MainKeyboardView_gestureTrailUpdateInterval, 0);
93         }
94     }
95 
96     // Use this value as imaginary zero because x-coordinates may be zero.
97     private static final int DOWN_EVENT_MARKER = -128;
98 
markAsDownEvent(final int xCoord)99     private static int markAsDownEvent(final int xCoord) {
100         return DOWN_EVENT_MARKER - xCoord;
101     }
102 
isDownEventXCoord(final int xCoordOrMark)103     private static boolean isDownEventXCoord(final int xCoordOrMark) {
104         return xCoordOrMark <= DOWN_EVENT_MARKER;
105     }
106 
getXCoordValue(final int xCoordOrMark)107     private static int getXCoordValue(final int xCoordOrMark) {
108         return isDownEventXCoord(xCoordOrMark)
109                 ? DOWN_EVENT_MARKER - xCoordOrMark : xCoordOrMark;
110     }
111 
addStroke(final GestureStrokeWithPreviewPoints stroke, final long downTime)112     public void addStroke(final GestureStrokeWithPreviewPoints stroke, final long downTime) {
113         synchronized (mEventTimes) {
114             addStrokeLocked(stroke, downTime);
115         }
116     }
117 
addStrokeLocked(final GestureStrokeWithPreviewPoints stroke, final long downTime)118     private void addStrokeLocked(final GestureStrokeWithPreviewPoints stroke, final long downTime) {
119         final int trailSize = mEventTimes.getLength();
120         stroke.appendPreviewStroke(mEventTimes, mXCoordinates, mYCoordinates);
121         if (mEventTimes.getLength() == trailSize) {
122             return;
123         }
124         final int[] eventTimes = mEventTimes.getPrimitiveArray();
125         final int strokeId = stroke.getGestureStrokeId();
126         // Because interpolation algorithm in {@link GestureStrokeWithPreviewPoints} can't determine
127         // the interpolated points in the last segment of gesture stroke, it may need recalculation
128         // of interpolation when new segments are added to the stroke.
129         // {@link #mLastInterpolatedDrawIndex} holds the start index of the last segment. It may
130         // be updated by the interpolation
131         // {@link GestureStrokeWithPreviewPoints#interpolatePreviewStroke}
132         // or by animation {@link #drawGestureTrail(Canvas,Paint,Rect,Params)} below.
133         final int lastInterpolatedIndex = (strokeId == mCurrentStrokeId)
134                 ? mLastInterpolatedDrawIndex : trailSize;
135         mLastInterpolatedDrawIndex = stroke.interpolateStrokeAndReturnStartIndexOfLastSegment(
136                 lastInterpolatedIndex, mEventTimes, mXCoordinates, mYCoordinates, mPointTypes);
137         if (strokeId != mCurrentStrokeId) {
138             final int elapsedTime = (int)(downTime - mCurrentTimeBase);
139             for (int i = mTrailStartIndex; i < trailSize; i++) {
140                 // Decay the previous strokes' event times.
141                 eventTimes[i] -= elapsedTime;
142             }
143             final int[] xCoords = mXCoordinates.getPrimitiveArray();
144             final int downIndex = trailSize;
145             xCoords[downIndex] = markAsDownEvent(xCoords[downIndex]);
146             mCurrentTimeBase = downTime - eventTimes[downIndex];
147             mCurrentStrokeId = strokeId;
148         }
149     }
150 
151     /**
152      * Calculate the alpha of a gesture trail.
153      * A gesture trail starts from fully opaque. After mFadeStartDelay has been passed, the alpha
154      * of a trail reduces in proportion to the elapsed time. Then after mFadeDuration has been
155      * passed, a trail becomes fully transparent.
156      *
157      * @param elapsedTime the elapsed time since a trail has been made.
158      * @param params gesture trail display parameters
159      * @return the width of a gesture trail
160      */
getAlpha(final int elapsedTime, final Params params)161     private static int getAlpha(final int elapsedTime, final Params params) {
162         if (elapsedTime < params.mFadeoutStartDelay) {
163             return Constants.Color.ALPHA_OPAQUE;
164         }
165         final int decreasingAlpha = Constants.Color.ALPHA_OPAQUE
166                 * (elapsedTime - params.mFadeoutStartDelay)
167                 / params.mFadeoutDuration;
168         return Constants.Color.ALPHA_OPAQUE - decreasingAlpha;
169     }
170 
171     /**
172      * Calculate the width of a gesture trail.
173      * A gesture trail starts from the width of mTrailStartWidth and reduces its width in proportion
174      * to the elapsed time. After mTrailEndWidth has been passed, the width becomes mTraiLEndWidth.
175      *
176      * @param elapsedTime the elapsed time since a trail has been made.
177      * @param params gesture trail display parameters
178      * @return the width of a gesture trail
179      */
getWidth(final int elapsedTime, final Params params)180     private static float getWidth(final int elapsedTime, final Params params) {
181         final float deltaWidth = params.mTrailStartWidth - params.mTrailEndWidth;
182         return params.mTrailStartWidth - (deltaWidth * elapsedTime) / params.mTrailLingerDuration;
183     }
184 
185     private final RoundedLine mRoundedLine = new RoundedLine();
186     private final Rect mRoundedLineBounds = new Rect();
187 
188     /**
189      * Draw gesture trail
190      * @param canvas The canvas to draw the gesture trail
191      * @param paint The paint object to be used to draw the gesture trail
192      * @param outBoundsRect the bounding box of this gesture trail drawing
193      * @param params The drawing parameters of gesture trail
194      * @return true if some gesture trails remain to be drawn
195      */
drawGestureTrail(final Canvas canvas, final Paint paint, final Rect outBoundsRect, final Params params)196     public boolean drawGestureTrail(final Canvas canvas, final Paint paint,
197             final Rect outBoundsRect, final Params params) {
198         synchronized (mEventTimes) {
199             return drawGestureTrailLocked(canvas, paint, outBoundsRect, params);
200         }
201     }
202 
drawGestureTrailLocked(final Canvas canvas, final Paint paint, final Rect outBoundsRect, final Params params)203     private boolean drawGestureTrailLocked(final Canvas canvas, final Paint paint,
204             final Rect outBoundsRect, final Params params) {
205         // Initialize bounds rectangle.
206         outBoundsRect.setEmpty();
207         final int trailSize = mEventTimes.getLength();
208         if (trailSize == 0) {
209             return false;
210         }
211 
212         final int[] eventTimes = mEventTimes.getPrimitiveArray();
213         final int[] xCoords = mXCoordinates.getPrimitiveArray();
214         final int[] yCoords = mYCoordinates.getPrimitiveArray();
215         final int[] pointTypes = mPointTypes.getPrimitiveArray();
216         final int sinceDown = (int)(SystemClock.uptimeMillis() - mCurrentTimeBase);
217         int startIndex;
218         for (startIndex = mTrailStartIndex; startIndex < trailSize; startIndex++) {
219             final int elapsedTime = sinceDown - eventTimes[startIndex];
220             // Skip too old trail points.
221             if (elapsedTime < params.mTrailLingerDuration) {
222                 break;
223             }
224         }
225         mTrailStartIndex = startIndex;
226 
227         if (startIndex < trailSize) {
228             paint.setColor(params.mTrailColor);
229             paint.setStyle(Paint.Style.FILL);
230             final RoundedLine roundedLine = mRoundedLine;
231             int p1x = getXCoordValue(xCoords[startIndex]);
232             int p1y = yCoords[startIndex];
233             final int lastTime = sinceDown - eventTimes[startIndex];
234             float r1 = getWidth(lastTime, params) / 2.0f;
235             for (int i = startIndex + 1; i < trailSize; i++) {
236                 final int elapsedTime = sinceDown - eventTimes[i];
237                 final int p2x = getXCoordValue(xCoords[i]);
238                 final int p2y = yCoords[i];
239                 final float r2 = getWidth(elapsedTime, params) / 2.0f;
240                 // Draw trail line only when the current point isn't a down point.
241                 if (!isDownEventXCoord(xCoords[i])) {
242                     final float body1 = r1 * params.mTrailBodyRatio;
243                     final float body2 = r2 * params.mTrailBodyRatio;
244                     final Path path = roundedLine.makePath(p1x, p1y, body1, p2x, p2y, body2);
245                     if (path != null) {
246                         roundedLine.getBounds(mRoundedLineBounds);
247                         if (params.mTrailShadowEnabled) {
248                             final float shadow2 = r2 * params.mTrailShadowRatio;
249                             paint.setShadowLayer(shadow2, 0.0f, 0.0f, params.mTrailColor);
250                             final int shadowInset = -(int)Math.ceil(shadow2);
251                             mRoundedLineBounds.inset(shadowInset, shadowInset);
252                         }
253                         // Take union for the bounds.
254                         outBoundsRect.union(mRoundedLineBounds);
255                         final int alpha = getAlpha(elapsedTime, params);
256                         paint.setAlpha(alpha);
257                         canvas.drawPath(path, paint);
258                         if (DBG_SHOW_POINTS) {
259                             if (pointTypes[i] == POINT_TYPE_INTERPOLATED) {
260                                 paint.setColor(Color.RED);
261                             } else if (pointTypes[i] == POINT_TYPE_SAMPLED) {
262                                 paint.setColor(0xFFA000FF);
263                             } else {
264                                 paint.setColor(Color.GREEN);
265                             }
266                             canvas.drawCircle(p1x - 1, p1y - 1, 2, paint);
267                             paint.setColor(params.mTrailColor);
268                         }
269                     }
270                 }
271                 p1x = p2x;
272                 p1y = p2y;
273                 r1 = r2;
274             }
275         }
276 
277         final int newSize = trailSize - startIndex;
278         if (newSize < startIndex) {
279             mTrailStartIndex = 0;
280             if (newSize > 0) {
281                 System.arraycopy(eventTimes, startIndex, eventTimes, 0, newSize);
282                 System.arraycopy(xCoords, startIndex, xCoords, 0, newSize);
283                 System.arraycopy(yCoords, startIndex, yCoords, 0, newSize);
284             }
285             mEventTimes.setLength(newSize);
286             mXCoordinates.setLength(newSize);
287             mYCoordinates.setLength(newSize);
288             if (DBG_SHOW_POINTS) {
289                 mPointTypes.setLength(newSize);
290             }
291             // The start index of the last segment of the stroke
292             // {@link mLastInterpolatedDrawIndex} should also be updated because all array
293             // elements have just been shifted for compaction or been zeroed.
294             mLastInterpolatedDrawIndex = Math.max(mLastInterpolatedDrawIndex - startIndex, 0);
295         }
296         return newSize > 0;
297     }
298 }
299