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