Home | History | Annotate | Download | only in focus
      1 /*
      2  * Copyright (C) 2014 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.camera.ui.focus;
     18 
     19 import android.graphics.Paint;
     20 
     21 import com.android.camera.debug.Log;
     22 import com.android.camera.debug.Log.Tag;
     23 import com.android.camera.ui.motion.DampedSpring;
     24 import com.android.camera.ui.motion.DynamicAnimation;
     25 import com.android.camera.ui.motion.Invalidator;
     26 import com.android.camera.ui.motion.UnitCurve;
     27 import com.android.camera.ui.motion.UnitCurves;
     28 
     29 /**
     30  * Base class for defining the focus ring states, enter and exit durations, and
     31  * positioning logic.
     32  */
     33 abstract class FocusRingRenderer implements DynamicAnimation {
     34     private static final Tag TAG = new Tag("FocusRingRenderer");
     35 
     36     /**
     37      * Primary focus states that a focus ring renderer can go through.
     38      */
     39     protected static enum FocusState {
     40         STATE_INACTIVE,
     41         STATE_ENTER,
     42         STATE_ACTIVE,
     43         STATE_FADE_OUT,
     44         STATE_HARD_STOP,
     45     }
     46 
     47     protected final Invalidator mInvalidator;
     48     protected final Paint mRingPaint;
     49     protected final DampedSpring mRingRadius;
     50     protected final UnitCurve mEnterOpacityCurve;
     51     protected final UnitCurve mExitOpacityCurve;
     52     protected final UnitCurve mHardExitOpacityCurve;
     53     protected final float mEnterDurationMillis;
     54     protected final float mExitDurationMillis;
     55     protected final float mHardExitDurationMillis = 64;
     56 
     57     private int mCenterX;
     58     private int mCenterY;
     59     protected long mEnterStartMillis = 0;
     60     protected long mExitStartMillis = 0;
     61     protected long mHardExitStartMillis = 0;
     62 
     63     protected FocusState mFocusState = FocusState.STATE_INACTIVE;
     64 
     65     /**
     66      * A dynamic, configurable, self contained ring render that will inform
     67      * via invalidation if it should continue to be receive updates
     68      * and re-draws.
     69      *
     70      * @param invalidator the object to inform if it requires more draw calls.
     71      * @param ringPaint the paint to use to draw the ring.
     72      * @param enterDurationMillis the fade in duration in milliseconds
     73      * @param exitDurationMillis the fade out duration in milliseconds.
     74      */
     75     FocusRingRenderer(Invalidator invalidator, Paint ringPaint, float enterDurationMillis,
     76           float exitDurationMillis) {
     77         mInvalidator = invalidator;
     78         mRingPaint = ringPaint;
     79         mEnterDurationMillis = enterDurationMillis;
     80         mExitDurationMillis = exitDurationMillis;
     81 
     82         mEnterOpacityCurve = UnitCurves.FAST_OUT_SLOW_IN;
     83         mExitOpacityCurve = UnitCurves.FAST_OUT_LINEAR_IN;
     84         mHardExitOpacityCurve = UnitCurves.FAST_OUT_LINEAR_IN;
     85 
     86         mRingRadius = new DampedSpring();
     87     }
     88 
     89     /**
     90      * Set the centerX position for this focus ring renderer.
     91      *
     92      * @param value the x position
     93      */
     94     public void setCenterX(int value) {
     95         mCenterX = value;
     96     }
     97 
     98     protected int getCenterX() {
     99         return mCenterX;
    100     }
    101 
    102     /**
    103      * Set the centerY position for this focus ring renderer.
    104      *
    105      * @param value the y position
    106      */
    107     public void setCenterY(int value) {
    108         mCenterY = value;
    109     }
    110 
    111     protected int getCenterY() {
    112         return mCenterY;
    113     }
    114 
    115     /**
    116      * Set the physical radius of this ring.
    117      *
    118      * @param value the radius of the ring.
    119      */
    120     public void setRadius(long tMs, float value) {
    121         if (mFocusState == FocusState.STATE_FADE_OUT
    122               && Math.abs(mRingRadius.getTarget() - value) > 0.1) {
    123             Log.v(TAG, "FOCUS STATE ENTER VIA setRadius(" + tMs + ", " + value + ")");
    124             mFocusState = FocusState.STATE_ENTER;
    125             mEnterStartMillis = computeEnterStartTimeMillis(tMs, mEnterDurationMillis);
    126         }
    127 
    128         mRingRadius.setTarget(value);
    129     }
    130 
    131     /**
    132      * returns true if the renderer is not in an inactive state.
    133      */
    134     @Override
    135     public boolean isActive() {
    136         return mFocusState != FocusState.STATE_INACTIVE;
    137     }
    138 
    139     /**
    140      * returns true if the renderer is in an exit state.
    141      */
    142     public boolean isExiting() {
    143         return mFocusState == FocusState.STATE_FADE_OUT
    144               || mFocusState == FocusState.STATE_HARD_STOP;
    145     }
    146 
    147     /**
    148      * returns true if the renderer is in an enter state.
    149      */
    150     public boolean isEntering() {
    151         return mFocusState == FocusState.STATE_ENTER;
    152     }
    153 
    154     /**
    155      * Initialize and start the animation with the given start and
    156      * target radius.
    157      */
    158     public void start(long startMs, float initialRadius, float targetRadius) {
    159         if (mFocusState != FocusState.STATE_INACTIVE) {
    160             Log.w(TAG, "start() called while the ring was still focusing!");
    161         }
    162         mRingRadius.stop();
    163         mRingRadius.setValue(initialRadius);
    164         mRingRadius.setTarget(targetRadius);
    165         mEnterStartMillis = startMs;
    166 
    167         mFocusState = FocusState.STATE_ENTER;
    168         mInvalidator.invalidate();
    169     }
    170 
    171     /**
    172      * Put the animation in the exit state regardless of the current
    173      * dynamic transition. If the animation is currently in an enter state
    174      * this will compute an exit start time such that the exit time lines
    175      * up with the enter time at the current transition value.
    176      *
    177      * @param t the current animation time.
    178      */
    179     public void exit(long t) {
    180         if (mRingRadius.isActive()) {
    181             mRingRadius.stop();
    182         }
    183 
    184         mFocusState = FocusState.STATE_FADE_OUT;
    185         mExitStartMillis = computeExitStartTimeMs(t, mExitDurationMillis);
    186     }
    187 
    188     /**
    189      * Put the animation in the hard stop state regardless of the current
    190      * dynamic transition. If the animation is currently in an enter state
    191      * this will compute an exit start time such that the exit time lines
    192      * up with the enter time at the current transition value.
    193      *
    194      * @param tMillis the current animation time in milliseconds.
    195      */
    196     public void stop(long tMillis) {
    197         if (mRingRadius.isActive()) {
    198             mRingRadius.stop();
    199         }
    200 
    201         mFocusState = FocusState.STATE_HARD_STOP;
    202         mHardExitStartMillis = computeExitStartTimeMs(tMillis, mHardExitDurationMillis);
    203     }
    204 
    205     private long computeExitStartTimeMs(long tMillis, float exitDuration) {
    206         if (mEnterStartMillis + mEnterDurationMillis <= tMillis) {
    207             return tMillis;
    208         }
    209 
    210         // Compute the current progress on the enter animation.
    211         float enterT = (tMillis - mEnterStartMillis) / mEnterDurationMillis;
    212 
    213         // Find a time on the exit curve such that it will produce the same value.
    214         float exitT = UnitCurves.mapEnterCurveToExitCurveAtT(mEnterOpacityCurve, mExitOpacityCurve,
    215               enterT);
    216 
    217         // Compute the a start time before tMs such that the ratio of time completed
    218         // equals the computed exit curve animation position.
    219         return tMillis - (long) (exitT * exitDuration);
    220     }
    221 
    222     private long computeEnterStartTimeMillis(long tMillis, float enterDuration) {
    223         if (mExitStartMillis + mExitDurationMillis <= tMillis) {
    224             return tMillis;
    225         }
    226 
    227         // Compute the current progress on the enter animation.
    228         float exitT = (tMillis - mExitStartMillis) / mExitDurationMillis;
    229 
    230         // Find a time on the exit curve such that it will produce the same value.
    231         float enterT = UnitCurves.mapEnterCurveToExitCurveAtT(mExitOpacityCurve, mEnterOpacityCurve,
    232               exitT);
    233 
    234         // Compute the a start time before tMs such that the ratio of time completed
    235         // equals the computed exit curve animation position.
    236         return tMillis - (long) (enterT * enterDuration);
    237     }
    238 }
    239