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 */
16package com.android.camera.widget;
17
18import com.google.common.base.Optional;
19
20import android.animation.Animator;
21import android.animation.AnimatorListenerAdapter;
22import android.animation.AnimatorSet;
23import android.animation.ValueAnimator;
24import android.content.Context;
25import android.content.res.Configuration;
26import android.graphics.Canvas;
27import android.graphics.Paint;
28import android.graphics.RectF;
29import android.util.AttributeSet;
30import android.view.View;
31import android.view.ViewGroup;
32import android.widget.FrameLayout;
33import android.widget.ImageButton;
34
35import com.android.camera.MultiToggleImageButton;
36import com.android.camera.ui.RadioOptions;
37import com.android.camera.ui.TopRightWeightedLayout;
38import com.android.camera.util.Gusterpolator;
39import com.android.camera2.R;
40
41import java.util.ArrayList;
42
43public class ModeOptions extends FrameLayout {
44    private int mBackgroundColor;
45    private final Paint mPaint = new Paint();
46    private boolean mIsHiddenOrHiding;
47    private RectF mAnimateFrom = new RectF();
48    private View mViewToShowHide;
49    private TopRightWeightedLayout mModeOptionsButtons;
50    private RadioOptions mModeOptionsPano;
51    private RadioOptions mModeOptionsExposure;
52
53    private AnimatorSet mVisibleAnimator;
54    private AnimatorSet mHiddenAnimator;
55    private boolean mDrawCircle;
56    private boolean mFill;
57    private static final int RADIUS_ANIMATION_TIME = 250;
58    private static final int SHOW_ALPHA_ANIMATION_TIME = 350;
59    private static final int HIDE_ALPHA_ANIMATION_TIME = 200;
60    public static final int PADDING_ANIMATION_TIME = 350;
61
62    private ViewGroup mMainBar;
63    private ViewGroup mActiveBar;
64    public static final int BAR_INVALID = -1;
65    public static final int BAR_STANDARD = 0;
66    public static final int BAR_PANO = 1;
67
68    private boolean mIsPortrait;
69    private float mRadius = 0f;
70
71    /**
72     * A class implementing this interface will receive callback events from
73     * mode options.
74     */
75    public interface Listener {
76        /**
77         * Called when about to start animating the mode options from hidden
78         * to visible.
79         */
80        public void onBeginToShowModeOptions();
81
82        /**
83         * Called when about to start animating the mode options from visible
84         * to hidden.
85         */
86        public void onBeginToHideModeOptions();
87    }
88
89    /** The listener. */
90    private Optional<Listener> mListener;
91
92    public ModeOptions(Context context, AttributeSet attrs) {
93        super(context, attrs);
94        mListener = Optional.absent();
95    }
96
97    /**
98     * Whether the mode options is hidden or in the middle of fading
99     * out.
100     */
101    public boolean isHiddenOrHiding() {
102        return mIsHiddenOrHiding;
103    }
104
105    /**
106     * Sets the listener.
107     *
108     * @param listener The listener to be set.
109     */
110    public void setListener(Listener listener) {
111        mListener = Optional.of(listener);
112    }
113
114    public void setViewToShowHide(View v) {
115        mViewToShowHide = v;
116    }
117
118    @Override
119    public void onFinishInflate() {
120        mIsHiddenOrHiding = true;
121        mBackgroundColor = getResources().getColor(R.color.mode_options_background);
122        mPaint.setAntiAlias(true);
123        mPaint.setColor(mBackgroundColor);
124        mModeOptionsButtons = (TopRightWeightedLayout) findViewById(R.id.mode_options_buttons);
125        mModeOptionsPano = (RadioOptions) findViewById(R.id.mode_options_pano);
126        mModeOptionsExposure = (RadioOptions) findViewById(R.id.mode_options_exposure);
127        mMainBar = mActiveBar = mModeOptionsButtons;
128    }
129
130    public void showExposureOptions() {
131        mActiveBar = mModeOptionsExposure;
132        mMainBar.setVisibility(View.INVISIBLE);
133        mActiveBar.setVisibility(View.VISIBLE);
134    }
135
136    public void setMainBar(int b) {
137        for (int i = 0; i < getChildCount(); i++) {
138            getChildAt(i).setVisibility(View.INVISIBLE);
139        }
140        switch (b) {
141        case BAR_STANDARD:
142            mMainBar = mActiveBar = mModeOptionsButtons;
143            break;
144        case BAR_PANO:
145            mMainBar = mActiveBar = mModeOptionsPano;
146            break;
147        }
148        mMainBar.setVisibility(View.VISIBLE);
149    }
150
151    public int getMainBar() {
152        if (mMainBar == mModeOptionsButtons) {
153            return BAR_STANDARD;
154        }
155        if (mMainBar == mModeOptionsPano) {
156            return BAR_PANO;
157        }
158        return BAR_INVALID;
159    }
160
161    @Override
162    public void onWindowVisibilityChanged(int visibility) {
163        super.onWindowVisibilityChanged(visibility);
164        if (visibility != VISIBLE && !mIsHiddenOrHiding) {
165            // Collapse mode options when window is not visible.
166            setVisibility(INVISIBLE);
167            if (mMainBar != null) {
168                mMainBar.setVisibility(VISIBLE);
169            }
170            if (mActiveBar != null && mActiveBar != mMainBar) {
171                mActiveBar.setVisibility(INVISIBLE);
172            }
173            if (mViewToShowHide != null) {
174                mViewToShowHide.setVisibility(VISIBLE);
175            }
176            mIsHiddenOrHiding = true;
177        }
178    }
179
180    @Override
181    public void onLayout(boolean changed, int left, int top, int right, int bottom) {
182        if (changed) {
183            mIsPortrait = (getResources().getConfiguration().orientation ==
184                           Configuration.ORIENTATION_PORTRAIT);
185
186            int buttonSize = getResources()
187                .getDimensionPixelSize(R.dimen.option_button_circle_size);
188            int buttonPadding = getResources()
189                .getDimensionPixelSize(R.dimen.mode_options_toggle_padding);
190
191            float rLeft, rRight, rTop, rBottom;
192            if (mIsPortrait) {
193                rLeft = getWidth() - buttonPadding - buttonSize;
194                rTop = (getHeight() - buttonSize) / 2.0f;
195            } else {
196                rLeft = buttonPadding;
197                rTop = buttonPadding;
198            }
199            rRight = rLeft + buttonSize;
200            rBottom = rTop + buttonSize;
201            mAnimateFrom.set(rLeft, rTop, rRight, rBottom);
202
203            setupAnimators();
204            setupToggleButtonParams();
205        }
206
207        super.onLayout(changed, left, top, right, bottom);
208    }
209
210    @Override
211    public void onDraw(Canvas canvas) {
212        if (mDrawCircle) {
213            canvas.drawCircle(mAnimateFrom.centerX(), mAnimateFrom.centerY(), mRadius, mPaint);
214        } else if (mFill) {
215            canvas.drawPaint(mPaint);
216        }
217        super.onDraw(canvas);
218    }
219
220    private void setupToggleButtonParams() {
221        int size = (mIsPortrait ? getHeight() : getWidth());
222
223        for (int i = 0; i < mModeOptionsButtons.getChildCount(); i++) {
224            View button = mModeOptionsButtons.getChildAt(i);
225            if (button instanceof MultiToggleImageButton) {
226                MultiToggleImageButton toggleButton = (MultiToggleImageButton) button;
227                toggleButton.setParentSize(size);
228                toggleButton.setAnimDirection(mIsPortrait ?
229                        MultiToggleImageButton.ANIM_DIRECTION_VERTICAL :
230                        MultiToggleImageButton.ANIM_DIRECTION_HORIZONTAL);
231            }
232        }
233    }
234
235    private void setupAnimators() {
236        if (mVisibleAnimator != null) {
237            mVisibleAnimator.end();
238        }
239        if (mHiddenAnimator != null) {
240            mHiddenAnimator.end();
241        }
242
243        final float fullSize = (mIsPortrait ? (float) getWidth() : (float) getHeight());
244
245        // show
246        {
247            final ValueAnimator radiusAnimator =
248                ValueAnimator.ofFloat(mAnimateFrom.width()/2.0f,
249                    fullSize-mAnimateFrom.width()/2.0f);
250            radiusAnimator.setDuration(RADIUS_ANIMATION_TIME);
251            radiusAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
252                @Override
253                public void onAnimationUpdate(ValueAnimator animation) {
254                    mRadius = (Float) animation.getAnimatedValue();
255                    mDrawCircle = true;
256                    mFill = false;
257                }
258            });
259            radiusAnimator.addListener(new AnimatorListenerAdapter() {
260                @Override
261                public void onAnimationEnd(Animator animation) {
262                    mDrawCircle = false;
263                    mFill = true;
264                }
265            });
266
267            final ValueAnimator alphaAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
268            alphaAnimator.setDuration(SHOW_ALPHA_ANIMATION_TIME);
269            alphaAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
270                @Override
271                public void onAnimationUpdate(ValueAnimator animation) {
272                    mActiveBar.setAlpha((Float) animation.getAnimatedValue());
273                }
274            });
275            alphaAnimator.addListener(new AnimatorListenerAdapter() {
276                @Override
277                public void onAnimationEnd(Animator animation) {
278                    mActiveBar.setAlpha(1.0f);
279                }
280            });
281
282            final int deltaX = getResources()
283                .getDimensionPixelSize(R.dimen.mode_options_buttons_anim_delta_x);
284            int childCount = mActiveBar.getChildCount();
285            ArrayList<Animator> paddingAnimators = new ArrayList<Animator>();
286            for (int i = 0; i < childCount; i++) {
287                final View button;
288                if (mIsPortrait) {
289                    button = mActiveBar.getChildAt(i);
290                } else {
291                    button = mActiveBar.getChildAt(childCount-1-i);
292                }
293
294                final ValueAnimator paddingAnimator =
295                    ValueAnimator.ofFloat(deltaX*(childCount-i), 0.0f);
296                paddingAnimator.setDuration(PADDING_ANIMATION_TIME);
297                paddingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
298                    @Override
299                    public void onAnimationUpdate(ValueAnimator animation) {
300                        if (mIsPortrait) {
301                            button.setTranslationX((Float) animation.getAnimatedValue());
302                        } else {
303                            button.setTranslationY(-((Float) animation.getAnimatedValue()));
304                        }
305                        invalidate();
306                    }
307                });
308
309                paddingAnimators.add(paddingAnimator);
310            }
311
312            AnimatorSet paddingAnimatorSet = new AnimatorSet();
313            paddingAnimatorSet.playTogether(paddingAnimators);
314
315            mVisibleAnimator = new AnimatorSet();
316            mVisibleAnimator.setInterpolator(Gusterpolator.INSTANCE);
317            mVisibleAnimator.playTogether(radiusAnimator, alphaAnimator, paddingAnimatorSet);
318        }
319
320        // hide
321        {
322            final ValueAnimator radiusAnimator =
323                ValueAnimator.ofFloat(fullSize-mAnimateFrom.width()/2.0f,
324                    mAnimateFrom.width()/2.0f);
325            radiusAnimator.setDuration(RADIUS_ANIMATION_TIME);
326            radiusAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
327                @Override
328                public void onAnimationUpdate(ValueAnimator animation) {
329                    mRadius = (Float) animation.getAnimatedValue();
330                    mDrawCircle = true;
331                    mFill = false;
332                    invalidate();
333                }
334            });
335            radiusAnimator.addListener(new AnimatorListenerAdapter() {
336                @Override
337                public void onAnimationEnd(Animator animation) {
338                    if (mViewToShowHide != null) {
339                        mViewToShowHide.setVisibility(View.VISIBLE);
340                        mDrawCircle = false;
341                        mFill = false;
342                        invalidate();
343                    }
344                }
345            });
346
347            final ValueAnimator alphaAnimator = ValueAnimator.ofFloat(1.0f, 0.0f);
348            alphaAnimator.setDuration(HIDE_ALPHA_ANIMATION_TIME);
349            alphaAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
350                @Override
351                public void onAnimationUpdate(ValueAnimator animation) {
352                    mActiveBar.setAlpha((Float) animation.getAnimatedValue());
353                    invalidate();
354                }
355            });
356            alphaAnimator.addListener(new AnimatorListenerAdapter() {
357                @Override
358                public void onAnimationEnd(Animator animation) {
359                    setVisibility(View.INVISIBLE);
360                    if (mActiveBar != mMainBar) {
361                        mActiveBar.setAlpha(1.0f);
362                        mActiveBar.setVisibility(View.INVISIBLE);
363                    }
364                    mMainBar.setAlpha(1.0f);
365                    mMainBar.setVisibility(View.VISIBLE);
366                    mActiveBar = mMainBar;
367                    invalidate();
368                }
369            });
370
371            mHiddenAnimator = new AnimatorSet();
372            mHiddenAnimator.setInterpolator(Gusterpolator.INSTANCE);
373            mHiddenAnimator.playTogether(radiusAnimator, alphaAnimator);
374        }
375    }
376
377    public void animateVisible() {
378        if (mIsHiddenOrHiding) {
379            if (mViewToShowHide != null) {
380                mViewToShowHide.setVisibility(View.INVISIBLE);
381            }
382            mHiddenAnimator.cancel();
383            mVisibleAnimator.end();
384            setVisibility(View.VISIBLE);
385            mVisibleAnimator.start();
386            if (mListener.isPresent()) {
387                mListener.get().onBeginToShowModeOptions();
388            }
389        }
390        mIsHiddenOrHiding = false;
391    }
392
393    public void animateHidden() {
394        if (!mIsHiddenOrHiding) {
395            mVisibleAnimator.cancel();
396            mHiddenAnimator.end();
397            mHiddenAnimator.start();
398            if (mListener.isPresent()) {
399                mListener.get().onBeginToHideModeOptions();
400            }
401        }
402        mIsHiddenOrHiding = true;
403    }
404}