Home | History | Annotate | Download | only in internal
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
      5  * in compliance with the License. You may obtain a copy of the License at
      6  *
      7  * http://www.apache.org/licenses/LICENSE-2.0
      8  *
      9  * Unless required by applicable law or agreed to in writing, software distributed under the License
     10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
     11  * or implied. See the License for the specific language governing permissions and limitations under
     12  * the License.
     13  */
     14 
     15 package com.android.inputmethod.keyboard.internal;
     16 
     17 import android.content.res.TypedArray;
     18 import android.graphics.Canvas;
     19 import android.graphics.Paint;
     20 import android.graphics.Path;
     21 import android.graphics.Rect;
     22 import android.graphics.RectF;
     23 import android.os.SystemClock;
     24 
     25 import com.android.inputmethod.latin.Constants;
     26 import com.android.inputmethod.latin.R;
     27 import com.android.inputmethod.latin.ResizableIntArray;
     28 
     29 final class GesturePreviewTrail {
     30     private static final int DEFAULT_CAPACITY = GestureStrokeWithPreviewPoints.PREVIEW_CAPACITY;
     31 
     32     private final ResizableIntArray mXCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
     33     private final ResizableIntArray mYCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
     34     private final ResizableIntArray mEventTimes = new ResizableIntArray(DEFAULT_CAPACITY);
     35     private int mCurrentStrokeId = -1;
     36     // The wall time of the zero value in {@link #mEventTimes}
     37     private long mCurrentTimeBase;
     38     private int mTrailStartIndex;
     39 
     40     static final class Params {
     41         public final int mTrailColor;
     42         public final float mTrailStartWidth;
     43         public final float mTrailEndWidth;
     44         public final int mFadeoutStartDelay;
     45         public final int mFadeoutDuration;
     46         public final int mUpdateInterval;
     47 
     48         public final int mTrailLingerDuration;
     49 
     50         public Params(final TypedArray keyboardViewAttr) {
     51             mTrailColor = keyboardViewAttr.getColor(
     52                     R.styleable.KeyboardView_gesturePreviewTrailColor, 0);
     53             mTrailStartWidth = keyboardViewAttr.getDimension(
     54                     R.styleable.KeyboardView_gesturePreviewTrailStartWidth, 0.0f);
     55             mTrailEndWidth = keyboardViewAttr.getDimension(
     56                     R.styleable.KeyboardView_gesturePreviewTrailEndWidth, 0.0f);
     57             mFadeoutStartDelay = keyboardViewAttr.getInt(
     58                     R.styleable.KeyboardView_gesturePreviewTrailFadeoutStartDelay, 0);
     59             mFadeoutDuration = keyboardViewAttr.getInt(
     60                     R.styleable.KeyboardView_gesturePreviewTrailFadeoutDuration, 0);
     61             mTrailLingerDuration = mFadeoutStartDelay + mFadeoutDuration;
     62             mUpdateInterval = keyboardViewAttr.getInt(
     63                     R.styleable.KeyboardView_gesturePreviewTrailUpdateInterval, 0);
     64         }
     65     }
     66 
     67     // Use this value as imaginary zero because x-coordinates may be zero.
     68     private static final int DOWN_EVENT_MARKER = -128;
     69 
     70     private static int markAsDownEvent(final int xCoord) {
     71         return DOWN_EVENT_MARKER - xCoord;
     72     }
     73 
     74     private static boolean isDownEventXCoord(final int xCoordOrMark) {
     75         return xCoordOrMark <= DOWN_EVENT_MARKER;
     76     }
     77 
     78     private static int getXCoordValue(final int xCoordOrMark) {
     79         return isDownEventXCoord(xCoordOrMark)
     80                 ? DOWN_EVENT_MARKER - xCoordOrMark : xCoordOrMark;
     81     }
     82 
     83     public void addStroke(final GestureStrokeWithPreviewPoints stroke, final long downTime) {
     84         final int trailSize = mEventTimes.getLength();
     85         stroke.appendPreviewStroke(mEventTimes, mXCoordinates, mYCoordinates);
     86         if (mEventTimes.getLength() == trailSize) {
     87             return;
     88         }
     89         final int[] eventTimes = mEventTimes.getPrimitiveArray();
     90         final int strokeId = stroke.getGestureStrokeId();
     91         if (strokeId != mCurrentStrokeId) {
     92             final int elapsedTime = (int)(downTime - mCurrentTimeBase);
     93             for (int i = mTrailStartIndex; i < trailSize; i++) {
     94                 // Decay the previous strokes' event times.
     95                 eventTimes[i] -= elapsedTime;
     96             }
     97             final int[] xCoords = mXCoordinates.getPrimitiveArray();
     98             final int downIndex = trailSize;
     99             xCoords[downIndex] = markAsDownEvent(xCoords[downIndex]);
    100             mCurrentTimeBase = downTime - eventTimes[downIndex];
    101             mCurrentStrokeId = strokeId;
    102         }
    103     }
    104 
    105     private static int getAlpha(final int elapsedTime, final Params params) {
    106         if (elapsedTime < params.mFadeoutStartDelay) {
    107             return Constants.Color.ALPHA_OPAQUE;
    108         }
    109         final int decreasingAlpha = Constants.Color.ALPHA_OPAQUE
    110                 * (elapsedTime - params.mFadeoutStartDelay)
    111                 / params.mFadeoutDuration;
    112         return Constants.Color.ALPHA_OPAQUE - decreasingAlpha;
    113     }
    114 
    115     private static float getWidth(final int elapsedTime, final Params params) {
    116         return Math.max((params.mTrailLingerDuration - elapsedTime)
    117                 * (params.mTrailStartWidth - params.mTrailEndWidth)
    118                 / params.mTrailLingerDuration, 0.0f);
    119     }
    120 
    121     static final class WorkingSet {
    122         // Input
    123         // Previous point (P1) coordinates and trail radius.
    124         public float p1x, p1y;
    125         public float r1;
    126         // Current point (P2) coordinates and trail radius.
    127         public float p2x, p2y;
    128         public float r2;
    129 
    130         // Output
    131         // Closing point of arc at P1.
    132         public float p1ax, p1ay;
    133         // Opening point of arc at P1.
    134         public float p1bx, p1by;
    135         // Opening point of arc at P2.
    136         public float p2ax, p2ay;
    137         // Closing point of arc at P2.
    138         public float p2bx, p2by;
    139         // Start angle of the trail arcs.
    140         public float aa;
    141         // Sweep angle of the trail arc at P1.
    142         public float a1;
    143         public RectF arc1 = new RectF();
    144         // Sweep angle of the trail arc at P2.
    145         public float a2;
    146         public RectF arc2 = new RectF();
    147     }
    148 
    149     private static final float RIGHT_ANGLE = (float)(Math.PI / 2.0d);
    150     private static final float RADIAN_TO_DEGREE = (float)(180.0d / Math.PI);
    151 
    152     private static boolean calculatePathPoints(final WorkingSet w) {
    153         final float dx = w.p2x - w.p1x;
    154         final float dy = w.p2y - w.p1y;
    155         // Distance of the points.
    156         final double l = Math.hypot(dx, dy);
    157         if (Double.compare(0.0d, l) == 0) {
    158             return false;
    159         }
    160         // Angle of the line p1-p2
    161         final float a = (float)Math.atan2(dy, dx);
    162         // Difference of trail cap radius.
    163         final float dr = w.r2 - w.r1;
    164         // Variation of angle at trail cap.
    165         final float ar = (float)Math.asin(dr / l);
    166         // The start angle of trail cap arc at P1.
    167         final float aa = a - (RIGHT_ANGLE + ar);
    168         // The end angle of trail cap arc at P2.
    169         final float ab = a + (RIGHT_ANGLE + ar);
    170         final float cosa = (float)Math.cos(aa);
    171         final float sina = (float)Math.sin(aa);
    172         final float cosb = (float)Math.cos(ab);
    173         final float sinb = (float)Math.sin(ab);
    174         w.p1ax = w.p1x + w.r1 * cosa;
    175         w.p1ay = w.p1y + w.r1 * sina;
    176         w.p1bx = w.p1x + w.r1 * cosb;
    177         w.p1by = w.p1y + w.r1 * sinb;
    178         w.p2ax = w.p2x + w.r2 * cosa;
    179         w.p2ay = w.p2y + w.r2 * sina;
    180         w.p2bx = w.p2x + w.r2 * cosb;
    181         w.p2by = w.p2y + w.r2 * sinb;
    182         w.aa = aa * RADIAN_TO_DEGREE;
    183         final float ar2degree = ar * 2.0f * RADIAN_TO_DEGREE;
    184         w.a1 = -180.0f + ar2degree;
    185         w.a2 = 180.0f + ar2degree;
    186         w.arc1.set(w.p1x, w.p1y, w.p1x, w.p1y);
    187         w.arc1.inset(-w.r1, -w.r1);
    188         w.arc2.set(w.p2x, w.p2y, w.p2x, w.p2y);
    189         w.arc2.inset(-w.r2, -w.r2);
    190         return true;
    191     }
    192 
    193     private static void createPath(final Path path, final WorkingSet w) {
    194         path.rewind();
    195         // Trail cap at P1.
    196         path.moveTo(w.p1x, w.p1y);
    197         path.arcTo(w.arc1, w.aa, w.a1);
    198         // Trail cap at P2.
    199         path.moveTo(w.p2x, w.p2y);
    200         path.arcTo(w.arc2, w.aa, w.a2);
    201         // Two trapezoids connecting P1 and P2.
    202         path.moveTo(w.p1ax, w.p1ay);
    203         path.lineTo(w.p1x, w.p1y);
    204         path.lineTo(w.p1bx, w.p1by);
    205         path.lineTo(w.p2bx, w.p2by);
    206         path.lineTo(w.p2x, w.p2y);
    207         path.lineTo(w.p2ax, w.p2ay);
    208         path.close();
    209     }
    210 
    211     private final WorkingSet mWorkingSet = new WorkingSet();
    212     private final Path mPath = new Path();
    213 
    214     /**
    215      * Draw gesture preview trail
    216      * @param canvas The canvas to draw the gesture preview trail
    217      * @param paint The paint object to be used to draw the gesture preview trail
    218      * @param outBoundsRect the bounding box of this gesture trail drawing
    219      * @param params The drawing parameters of gesture preview trail
    220      * @return true if some gesture preview trails remain to be drawn
    221      */
    222     public boolean drawGestureTrail(final Canvas canvas, final Paint paint,
    223             final Rect outBoundsRect, final Params params) {
    224         final int trailSize = mEventTimes.getLength();
    225         if (trailSize == 0) {
    226             return false;
    227         }
    228 
    229         final int[] eventTimes = mEventTimes.getPrimitiveArray();
    230         final int[] xCoords = mXCoordinates.getPrimitiveArray();
    231         final int[] yCoords = mYCoordinates.getPrimitiveArray();
    232         final int sinceDown = (int)(SystemClock.uptimeMillis() - mCurrentTimeBase);
    233         int startIndex;
    234         for (startIndex = mTrailStartIndex; startIndex < trailSize; startIndex++) {
    235             final int elapsedTime = sinceDown - eventTimes[startIndex];
    236             // Skip too old trail points.
    237             if (elapsedTime < params.mTrailLingerDuration) {
    238                 break;
    239             }
    240         }
    241         mTrailStartIndex = startIndex;
    242 
    243         if (startIndex < trailSize) {
    244             paint.setColor(params.mTrailColor);
    245             paint.setStyle(Paint.Style.FILL);
    246             final Path path = mPath;
    247             final WorkingSet w = mWorkingSet;
    248             w.p1x = getXCoordValue(xCoords[startIndex]);
    249             w.p1y = yCoords[startIndex];
    250             int lastTime = sinceDown - eventTimes[startIndex];
    251             float maxWidth = getWidth(lastTime, params);
    252             w.r1 = maxWidth / 2.0f;
    253             // Initialize bounds rectangle.
    254             outBoundsRect.set((int)w.p1x, (int)w.p1y, (int)w.p1x, (int)w.p1y);
    255             for (int i = startIndex + 1; i < trailSize - 1; i++) {
    256                 final int elapsedTime = sinceDown - eventTimes[i];
    257                 w.p2x = getXCoordValue(xCoords[i]);
    258                 w.p2y = yCoords[i];
    259                 // Draw trail line only when the current point isn't a down point.
    260                 if (!isDownEventXCoord(xCoords[i])) {
    261                     final int alpha = getAlpha(elapsedTime, params);
    262                     paint.setAlpha(alpha);
    263                     final float width = getWidth(elapsedTime, params);
    264                     w.r2 = width / 2.0f;
    265                     if (calculatePathPoints(w)) {
    266                         createPath(path, w);
    267                         canvas.drawPath(path, paint);
    268                         outBoundsRect.union((int)w.p2x, (int)w.p2y);
    269                     }
    270                     // Take union for the bounds.
    271                     maxWidth = Math.max(maxWidth, width);
    272                 }
    273                 w.p1x = w.p2x;
    274                 w.p1y = w.p2y;
    275                 w.r1 = w.r2;
    276                 lastTime = elapsedTime;
    277             }
    278             // Take care of trail line width.
    279             final int inset = -((int)maxWidth + 1);
    280             outBoundsRect.inset(inset, inset);
    281         }
    282 
    283         final int newSize = trailSize - startIndex;
    284         if (newSize < startIndex) {
    285             mTrailStartIndex = 0;
    286             if (newSize > 0) {
    287                 System.arraycopy(eventTimes, startIndex, eventTimes, 0, newSize);
    288                 System.arraycopy(xCoords, startIndex, xCoords, 0, newSize);
    289                 System.arraycopy(yCoords, startIndex, yCoords, 0, newSize);
    290             }
    291             mEventTimes.setLength(newSize);
    292             mXCoordinates.setLength(newSize);
    293             mYCoordinates.setLength(newSize);
    294         }
    295         return newSize > 0;
    296     }
    297 }
    298