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
17package com.android.camera.ui.focus;
18
19import android.graphics.Paint;
20
21import com.android.camera.debug.Log;
22import com.android.camera.debug.Log.Tag;
23import com.android.camera.ui.motion.DampedSpring;
24import com.android.camera.ui.motion.DynamicAnimation;
25import com.android.camera.ui.motion.Invalidator;
26import com.android.camera.ui.motion.UnitCurve;
27import 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 */
33abstract 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