1/*
2 * Copyright (C) 2010 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.videoeditor.widgets;
18
19import java.util.ArrayList;
20import java.util.List;
21
22import com.android.videoeditor.R;
23
24import android.app.Activity;
25import android.content.Context;
26import android.content.res.Resources;
27import android.graphics.Canvas;
28import android.graphics.drawable.Drawable;
29import android.os.Handler;
30import android.os.SystemClock;
31import android.util.AttributeSet;
32import android.view.Display;
33import android.view.MotionEvent;
34import android.view.ScaleGestureDetector;
35
36/**
37 * The timeline scroll view
38 */
39public class TimelineHorizontalScrollView extends HorizontalScrollView {
40    public final static int PLAYHEAD_NORMAL = 1;
41    public final static int PLAYHEAD_MOVE_OK = 2;
42    public final static int PLAYHEAD_MOVE_NOT_OK = 3;
43
44    // Instance variables
45    private final List<ScrollViewListener> mScrollListenerList;
46    private final Handler mHandler;
47    private final int mPlayheadMarginTop;
48    private final int mPlayheadMarginTopOk;
49    private final int mPlayheadMarginTopNotOk;
50    private final int mPlayheadMarginBottom;
51    private final Drawable mNormalPlayheadDrawable;
52    private final Drawable mMoveOkPlayheadDrawable;
53    private final Drawable mMoveNotOkPlayheadDrawable;
54    private final int mHalfParentWidth;
55    private ScaleGestureDetector mScaleDetector;
56    private int mLastScrollX;
57    private boolean mIsScrolling;
58    private boolean mAppScroll;
59    private boolean mEnableUserScrolling;
60
61    // The runnable which executes when the scrolling ends
62    private Runnable mScrollEndedRunnable = new Runnable() {
63        @Override
64        public void run() {
65            mIsScrolling = false;
66
67            for (ScrollViewListener listener : mScrollListenerList) {
68                listener.onScrollEnd(TimelineHorizontalScrollView.this, getScrollX(),
69                        getScrollY(), mAppScroll);
70            }
71
72            mAppScroll = false;
73        }
74    };
75
76    public TimelineHorizontalScrollView(Context context, AttributeSet attrs, int defStyle) {
77        super(context, attrs, defStyle);
78
79        mEnableUserScrolling = true;
80        mScrollListenerList = new ArrayList<ScrollViewListener>();
81        mHandler = new Handler();
82
83        // Compute half the width of the screen (and therefore the parent view)
84        final Display display = ((Activity)context).getWindowManager().getDefaultDisplay();
85        mHalfParentWidth = display.getWidth() / 2;
86
87        // This value is shared by all children. It represents the width of
88        // the left empty view.
89        setTag(R.id.left_view_width, mHalfParentWidth);
90        setTag(R.id.playhead_offset, -1);
91        setTag(R.id.playhead_type, PLAYHEAD_NORMAL);
92
93        final Resources resources = context.getResources();
94
95        // Get the playhead margins
96        mPlayheadMarginTop = (int)resources.getDimension(R.dimen.playhead_margin_top);
97        mPlayheadMarginBottom = (int)resources.getDimension(R.dimen.playhead_margin_bottom);
98        mPlayheadMarginTopOk = (int)resources.getDimension(R.dimen.playhead_margin_top_ok);
99        mPlayheadMarginTopNotOk = (int)resources.getDimension(R.dimen.playhead_margin_top_not_ok);
100
101        // Prepare the playhead drawable
102        mNormalPlayheadDrawable = resources.getDrawable(R.drawable.ic_playhead);
103        mMoveOkPlayheadDrawable = resources.getDrawable(R.drawable.playhead_move_ok);
104        mMoveNotOkPlayheadDrawable = resources.getDrawable(R.drawable.playhead_move_not_ok);
105    }
106
107    public TimelineHorizontalScrollView(Context context, AttributeSet attrs) {
108        this(context, attrs, 0);
109    }
110
111    public TimelineHorizontalScrollView(Context context) {
112        this(context, null, 0);
113    }
114
115    /**
116     * Invoked to enable/disable user scrolling (as opposed to programmatic scrolling)
117     * @param enable true to enable user scrolling
118     */
119    public void enableUserScrolling(boolean enable) {
120        mEnableUserScrolling = enable;
121    }
122
123    @Override
124    public boolean onInterceptTouchEvent(MotionEvent ev) {
125        mScaleDetector.onTouchEvent(ev);
126        return super.onInterceptTouchEvent(ev);
127    }
128
129    @Override
130    public boolean onTouchEvent(MotionEvent ev) {
131        if (mEnableUserScrolling) {
132            mScaleDetector.onTouchEvent(ev);
133            return super.onTouchEvent(ev);
134        } else {
135            if (mScaleDetector.isInProgress()) {
136                final MotionEvent cancelEvent = MotionEvent.obtain(SystemClock.uptimeMillis(),
137                        SystemClock.uptimeMillis(), MotionEvent.ACTION_CANCEL, 0, 0, 0);
138                mScaleDetector.onTouchEvent(cancelEvent);
139                cancelEvent.recycle();
140            }
141            return true;
142        }
143    }
144
145    /**
146     * @param listener The scale listener
147     */
148    public void setScaleListener(ScaleGestureDetector.SimpleOnScaleGestureListener listener) {
149        mScaleDetector = new ScaleGestureDetector(getContext(), listener);
150    }
151
152    /**
153     * @param listener The listener
154     */
155    public void addScrollListener(ScrollViewListener listener) {
156        mScrollListenerList.add(listener);
157    }
158
159    /**
160     * @param listener The listener
161     */
162    public void removeScrollListener(ScrollViewListener listener) {
163        mScrollListenerList.remove(listener);
164    }
165
166    /**
167     * @return true if scrolling is in progress
168     */
169    public boolean isScrolling() {
170        return mIsScrolling;
171    }
172
173    /**
174     * The app wants to scroll (as opposed to the user)
175     *
176     * @param scrollX Horizontal scroll position
177     * @param smooth true to scroll smoothly
178     */
179    public void appScrollTo(int scrollX, boolean smooth) {
180        if (getScrollX() == scrollX) {
181            return;
182        }
183
184        mAppScroll = true;
185
186        if (smooth) {
187            smoothScrollTo(scrollX, 0);
188        } else {
189            scrollTo(scrollX, 0);
190        }
191    }
192
193    /**
194     * The app wants to scroll (as opposed to the user)
195     *
196     * @param scrollX Horizontal scroll offset
197     * @param smooth true to scroll smoothly
198     */
199    public void appScrollBy(int scrollX, boolean smooth) {
200        mAppScroll = true;
201
202        if (smooth) {
203            smoothScrollBy(scrollX, 0);
204        } else {
205            scrollBy(scrollX, 0);
206        }
207    }
208
209    @Override
210    public void computeScroll() {
211        super.computeScroll();
212
213        final int scrollX = getScrollX();
214        if (mLastScrollX != scrollX) {
215            mLastScrollX = scrollX;
216
217            // Cancel the previous event
218            mHandler.removeCallbacks(mScrollEndedRunnable);
219
220            // Post a new event
221            mHandler.postDelayed(mScrollEndedRunnable, 300);
222
223            final int scrollY = getScrollY();
224            if (mIsScrolling) {
225                for (ScrollViewListener listener : mScrollListenerList) {
226                    listener.onScrollProgress(this, scrollX, scrollY, mAppScroll);
227                }
228            } else {
229                mIsScrolling = true;
230
231                for (ScrollViewListener listener : mScrollListenerList) {
232                    listener.onScrollBegin(this, scrollX, scrollY, mAppScroll);
233                }
234            }
235        }
236    }
237
238    @Override
239    protected void dispatchDraw(Canvas canvas) {
240        super.dispatchDraw(canvas);
241
242        final int playheadOffset = (Integer)getTag(R.id.playhead_offset);
243        final int startX;
244        if (playheadOffset < 0) {
245            // Draw the playhead in the middle of the screen
246            startX = mHalfParentWidth + getScrollX();
247        } else {
248            // Draw the playhead at the specified position (during trimming)
249            startX = playheadOffset;
250        }
251
252        final int playheadType = (Integer)getTag(R.id.playhead_type);
253        final int halfPlayheadWidth = mNormalPlayheadDrawable.getIntrinsicWidth() / 2;
254        switch (playheadType) {
255            case PLAYHEAD_NORMAL: {
256                // Draw the normal playhead
257                mNormalPlayheadDrawable.setBounds(
258                        startX - halfPlayheadWidth,
259                        mPlayheadMarginTop,
260                        startX + halfPlayheadWidth,
261                        getHeight() - mPlayheadMarginBottom);
262                mNormalPlayheadDrawable.draw(canvas);
263                break;
264            }
265
266            case PLAYHEAD_MOVE_OK: {
267                // Draw the move playhead
268                mMoveOkPlayheadDrawable.setBounds(
269                        startX - halfPlayheadWidth,
270                        mPlayheadMarginTopOk,
271                        startX + halfPlayheadWidth,
272                        mPlayheadMarginTopOk + mMoveOkPlayheadDrawable.getIntrinsicHeight());
273                mMoveOkPlayheadDrawable.draw(canvas);
274                break;
275            }
276
277            case PLAYHEAD_MOVE_NOT_OK: {
278                // Draw the move playhead
279                mMoveNotOkPlayheadDrawable.setBounds(
280                        startX - halfPlayheadWidth,
281                        mPlayheadMarginTopNotOk,
282                        startX + halfPlayheadWidth,
283                        mPlayheadMarginTopNotOk + mMoveNotOkPlayheadDrawable.getIntrinsicHeight());
284                mMoveNotOkPlayheadDrawable.draw(canvas);
285                break;
286            }
287
288            default: {
289                break;
290            }
291        }
292    }
293}
294