OSDN Git Service

Initial commit of 3D gallery source code to project.
[android-x86/packages-apps-Gallery2.git] / src / com / cooliris / media / TimeBar.java
1 package com.cooliris.media;
2
3 import java.util.ArrayList;
4 import java.util.Calendar;
5 import java.util.GregorianCalendar;
6 import java.util.HashMap;
7
8 import javax.microedition.khronos.opengles.GL11;
9
10 import android.content.Context;
11 import android.graphics.Bitmap;
12 import android.graphics.BitmapFactory;
13 import android.graphics.Canvas;
14 import android.graphics.NinePatch;
15 import android.graphics.Paint;
16 import android.graphics.PorterDuff;
17 import android.graphics.PorterDuffXfermode;
18 import android.graphics.Rect;
19 import android.util.SparseArray;
20 import android.view.MotionEvent;
21 import com.cooliris.media.RenderView.Lists;
22
23 public final class TimeBar extends Layer implements MediaFeed.Listener {
24     public static final int HEIGHT = 48;
25     private static final int MARKER_SPACING_PIXELS = 50;
26     private static final float AUTO_SCROLL_MARGIN = 100f;
27     private static final Paint SRC_PAINT = new Paint();
28     private final Context mContext;
29     private Listener mListener = null;
30     private MediaFeed mFeed = null;
31     private float mTotalWidth = 0f;
32     private float mPosition = 0f;
33     private float mPositionAnim = 0f;
34     private float mScroll = 0f;
35     private float mScrollAnim = 0f;
36     private boolean mInDrag = false;
37     private float mDragX = 0f;
38
39     private ArrayList<Marker> mMarkers = new ArrayList<Marker>();
40     private ArrayList<Marker> mMarkersCopy = new ArrayList<Marker>();
41
42     private static final int KNOB = R.drawable.scroller_new;
43     private static final int KNOB_PRESSED = R.drawable.scroller_pressed_new;
44     private final StringTexture.Config mMonthYearFormat = new StringTexture.Config();
45     private final StringTexture.Config mDayFormat = new StringTexture.Config();
46     private final SparseArray<StringTexture> mYearLabels = new SparseArray<StringTexture>();
47     private final StringTexture mDateUnknown;
48     private final StringTexture[] mMonthLabels = new StringTexture[12];
49     private final StringTexture[] mDayLabels = new StringTexture[32];
50     private final StringTexture[] mOpaqueDayLabels = new StringTexture[32];
51     private final StringTexture mDot = new StringTexture("¥");
52     private final HashMap<MediaItem, Marker> mTracker = new HashMap<MediaItem, Marker>(1024);
53     private int mState;
54     private float mTextAlpha = 0.0f;
55     private float mAnimTextAlpha = 0.0f;
56     private boolean mShowTime;
57     private NinePatch mBackground;
58     private Rect mBackgroundRect;
59     private BitmapTexture mBackgroundTexture;
60
61     public interface Listener {
62         public void onTimeChanged(TimeBar timebar);
63     }
64
65     TimeBar(Context context) {
66         // Save the context.
67         mContext = context;
68
69         // Setup formatting for text labels.
70         mMonthYearFormat.fontSize = 17f * Gallery.PIXEL_DENSITY;
71         mMonthYearFormat.bold = true;
72         mMonthYearFormat.a = 0.85f;
73         mDayFormat.fontSize = 17f * Gallery.PIXEL_DENSITY;
74         mDayFormat.a = 0.61f;
75
76         // Create textures for month names.
77         String[] months = mContext.getResources().getStringArray(R.array.months_abbreviated);
78         for (int i = 0; i < months.length; ++i) {
79             mMonthLabels[i] = new StringTexture(months[i], mMonthYearFormat);
80         }
81
82         for (int i = 0; i <= 31; ++i) {
83             mDayLabels[i] = new StringTexture(Integer.toString(i), mDayFormat);
84             mOpaqueDayLabels[i] = new StringTexture(Integer.toString(i), mMonthYearFormat);
85         }
86         mDateUnknown = new StringTexture(context.getResources().getString(R.string.date_unknown), mMonthYearFormat);
87         Bitmap background = BitmapFactory.decodeResource(context.getResources(), R.drawable.popup);
88         mBackground = new NinePatch(background, background.getNinePatchChunk(), null);
89         mBackgroundRect = new Rect();
90         SRC_PAINT.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
91     }
92
93     public void setListener(Listener listener) {
94         mListener = listener;
95     }
96
97     public void setFeed(MediaFeed feed, int state, boolean needsLayout) {
98         mFeed = feed;
99         mState = state;
100         layout();
101         if (needsLayout) {
102             mPosition = 0;
103             mScroll = getScrollForPosition(mPosition);
104         }
105     }
106
107     @Override
108     protected void onSizeChanged() {
109         mScroll = getScrollForPosition(mPosition);
110     }
111
112     // public long getTime() {
113     // final float x = mPosition * mTotalWidth;
114     // for (int i = 0, size = mMarkers.size(); i < size; ++i) {
115     // Marker marker = mMarkers.get(i);
116     // if (x <= marker.x) {
117     // return marker.time;
118     // }
119     // }
120     // return 0;
121     // }
122     //
123     // public void setTime(long time) {
124     // synchronized (mMarkers) {
125     // for (int i = 0, size = mMarkers.size(); i < size; ++i) {
126     // Marker marker = mMarkers.get(i);
127     // if (time <= marker.time) {
128     // mPosition = Math.max(0.0f, Math.min(1.0f, marker.x / mTotalWidth));
129     // mScroll = getScrollForPosition(mPosition);
130     // break;
131     // }
132     // }
133     // }
134     // }
135
136     public MediaItem getItem() {
137         synchronized (mMarkers) {
138             // x is between 0 and 1.0f
139             int numMarkers = mMarkers.size();
140             if (numMarkers == 0)
141                 return null;
142             int index = (int) (mPosition * (numMarkers));
143             if (index >= numMarkers)
144                 index = numMarkers - 1;
145             Marker marker = mMarkers.get(index);
146             if (marker != null) {
147                 // we have to find the index of the media item depending upon
148                 // the value of mPosition
149                 float deltaBetweenMarkers = 1.0f / numMarkers;
150                 float increment = mPosition - index * deltaBetweenMarkers;
151                 // if (increment > deltaBetweenMarkers)
152                 // increment = deltaBetweenMarkers;
153                 // if (increment < 0)
154                 // increment = 0;
155                 ArrayList<MediaItem> items = marker.items;
156                 int numItems = items.size();
157                 if (numItems == 0)
158                     return null;
159                 int itemIndex = (int) ((numItems) * increment / deltaBetweenMarkers);
160                 if (itemIndex >= numItems)
161                     itemIndex = numItems - 1;
162                 return marker.items.get(itemIndex);
163             }
164         }
165         return null;
166     }
167
168     private Marker getAnchorMarker() {
169         synchronized (mMarkers) {
170             // x is between 0 and 1.0f
171             int numMarkers = mMarkers.size();
172             if (numMarkers == 0)
173                 return null;
174             int index = (int) (mPosition * (numMarkers));
175             if (index >= numMarkers)
176                 index = numMarkers - 1;
177             Marker marker = mMarkers.get(index);
178             return marker;
179         }
180     }
181
182     public void setItem(MediaItem item) {
183         Marker marker = mTracker.get(item);
184         if (marker != null) {
185             float markerX = (mTotalWidth == 0.0f) ? 0.0f : marker.x / mTotalWidth;
186             mPosition = Math.max(0.0f, Math.min(1.0f, markerX));
187             mScroll = getScrollForPosition(mPosition);
188         }
189     }
190
191     private void layout() {
192         if (mFeed != null) {
193             // Clear existing markers.
194             mTracker.clear();
195             synchronized (mMarkers) {
196                 mMarkers.clear();
197             }
198             float scrollX = mScroll;
199             // Place markers for every time interval that intersects one of the
200             // clusters.
201             // Markers for a full month would be for example: Jan 5 10 15 20 25
202             // 30.
203             MediaFeed feed = mFeed;
204             int lastYear = -1;
205             int lastMonth = -1;
206             int lastDayBlock = -1;
207             float dx = 0f;
208             int increment = 12;
209             MediaSet set = null;
210             mShowTime = true;
211             if (mState == GridLayer.STATE_GRID_VIEW) {
212                 set = feed.getFilteredSet();
213                 if (set == null) {
214                     set = feed.getCurrentSet();
215                 }
216             } else {
217                 increment = 2;
218                 if (!feed.hasExpandedMediaSet()) {
219                     mShowTime = false;
220                 }
221                 set = new MediaSet();
222                 int numSlots = feed.getNumSlots();
223                 for (int i = 0; i < numSlots; ++i) {
224                     MediaSet slotSet = feed.getSetForSlot(i);
225                     if (slotSet != null) {
226                         ArrayList<MediaItem> slotSetItems = slotSet.getItems();
227                         if (slotSetItems != null && slotSet.getNumItems() > 0) {
228                             MediaItem item = slotSetItems.get(0);
229                             if (item != null) {
230                                 set.addItem(item);
231                             }
232                         }
233                     }
234                 }
235             }
236             if (set != null) {
237                 GregorianCalendar time = new GregorianCalendar();
238                 ArrayList<MediaItem> items = set.getItems();
239                 if (items != null) {
240                     int j = 0;
241                     while (j < set.getNumItems()) {
242                         final MediaItem item = items.get(j);
243                         if (item == null)
244                             continue;
245                         time.setTimeInMillis(item.mDateTakenInMs);
246                         // Detect year rollovers.
247                         final int year = time.get(Calendar.YEAR);
248                         if (year != lastYear) {
249                             lastYear = year;
250                             lastMonth = -1;
251                             lastDayBlock = -1;
252                         }
253                         Marker marker = null;
254                         // Detect month rollovers and emit a month marker.
255                         final int month = time.get(Calendar.MONTH);
256                         final int dayBlock = time.get(Calendar.DATE);
257                         if (month != lastMonth) {
258                             lastMonth = month;
259                             lastDayBlock = -1;
260                             marker = new Marker(mMonthLabels[month], dx, time.getTimeInMillis(), year, month, dayBlock,
261                                     Marker.TYPE_MONTH, increment);
262                             dx = addMarker(marker);
263                         } else if (dayBlock != lastDayBlock) {
264                             lastDayBlock = dayBlock;
265                             if (dayBlock != 0) {
266                                 marker = new Marker(mDayLabels[dayBlock], dx, time.getTimeInMillis(), year, month, dayBlock,
267                                         Marker.TYPE_DAY, increment);
268                                 dx = addMarker(marker);
269                             }
270                         } else {
271                             marker = new Marker(mDot, dx, time.getTimeInMillis(), year, month, dayBlock, Marker.TYPE_DOT, increment);
272                             dx = addMarker(marker);
273                         }
274                         for (int k = 0; k < increment; ++k) {
275                             int index = k + j;
276                             if (index < 0)
277                                 continue;
278                             if (index >= items.size())
279                                 break;
280                             if (index == items.size() - 1 && k != 0)
281                                 break;
282                             MediaItem thisItem = items.get(index);
283                             marker.items.add(thisItem);
284                             mTracker.put(thisItem, marker);
285                         }
286                         if (j == items.size() - 1)
287                             break;
288                         j += increment;
289                         if (j >= items.size() - 1)
290                             j = items.size() - 1;
291                     }
292                 }
293                 mTotalWidth = dx - MARKER_SPACING_PIXELS * Gallery.PIXEL_DENSITY;
294             }
295             mPosition = getPositionForScroll(scrollX);
296             mPositionAnim = mPosition;
297             synchronized (mMarkersCopy) {
298                 int numMarkers = mMarkers.size();
299                 mMarkersCopy.clear();
300                 mMarkersCopy.ensureCapacity(numMarkers);
301                 for (int i = 0; i < numMarkers; ++i) {
302                     mMarkersCopy.add(mMarkers.get(i));
303                 }
304             }
305         }
306     }
307
308     private float addMarker(Marker marker) {
309         mMarkers.add(marker);
310         return marker.x + MARKER_SPACING_PIXELS * Gallery.PIXEL_DENSITY;
311     }
312
313     /*
314      * private float getKnobXForPosition(float position) { return position * (mTotalWidth - mKnob.getWidth()); }
315      * 
316      * private float getPositionForKnobX(float knobX) { return Math.max(0f, Math.min(1f, knobX / (mTotalWidth - mKnob.getWidth())));
317      * }
318      * 
319      * private float getScrollForPosition(float position) { return position * (mTotalWidth - mWidth);// - (1f - 2f * position) *
320      * MARKER_SPACING_PIXELS; }
321      */
322
323     private float getScrollForPosition(float position) {
324         // Map position [0, 1] to scroll [-visibleWidth/2, totalWidth -
325         // visibleWidth/2].
326         // This has the effect of centering the scroll knob on screen.
327         float halfWidth = mWidth * 0.5f;
328         float positionInv = 1f - position;
329         float centered = positionInv * -halfWidth + position * (mTotalWidth - halfWidth);
330         return centered;
331     }
332
333     private float getPositionForScroll(float scroll) {
334         float halfWidth = mWidth * 0.5f;
335         if (mTotalWidth == 0)
336             return 0;
337         return ((scroll + halfWidth) / (mTotalWidth));
338     }
339
340     private float getKnobXForPosition(float position) {
341         return position * mTotalWidth;
342     }
343
344     private float getPositionForKnobX(float knobX) {
345         float normKnobX = (mTotalWidth == 0) ? 0 : knobX / mTotalWidth;
346         return Math.max(0f, Math.min(1f, normKnobX));
347     }
348
349     @Override
350     public boolean update(RenderView view, float dt) {
351         // Update animations.
352         final float ratio = Math.min(1f, 10f * dt);
353         final float invRatio = 1f - ratio;
354         mPositionAnim = ratio * mPosition + invRatio * mPositionAnim;
355         mScrollAnim = ratio * mScroll + invRatio * mScrollAnim;
356         // Handle autoscroll.
357         if (mInDrag) {
358             final float x = getKnobXForPosition(mPosition) - mScrollAnim;
359             float velocity;
360             float autoScrollMargin = AUTO_SCROLL_MARGIN * Gallery.PIXEL_DENSITY;
361             if (x < autoScrollMargin) {
362                 velocity = -(float) Math.pow((1f - x / autoScrollMargin), 2);
363             } else if (x > mWidth - autoScrollMargin) {
364                 velocity = (float) Math.pow(1f - (mWidth - x) / autoScrollMargin, 2);
365             } else {
366                 velocity = 0;
367             }
368             mScroll += velocity * 400f * dt;
369             mPosition = getPositionForKnobX(mDragX + mScroll);
370             mTextAlpha = 1.0f;
371         } else {
372             mTextAlpha = 0.0f;
373         }
374         mAnimTextAlpha = FloatUtils.animate(mAnimTextAlpha, mTextAlpha, dt);
375         return mAnimTextAlpha != mTextAlpha;
376     }
377
378     @Override
379     public void renderBlended(RenderView view, GL11 gl) {
380         final float originX = mX;
381         final float originY = mY;
382         final float scrollOffset = mScrollAnim;
383         final float scrolledOriginX = originX - scrollOffset;
384         final float position = mPositionAnim;
385         final int knobId = mInDrag ? KNOB_PRESSED : KNOB;
386         final Texture knob = view.getResource(knobId);
387         // Draw the scroller knob.
388         if (!mShowTime) {
389             if (view.bind(knob)) {
390                 final float knobWidth = knob.getWidth();
391                 view.draw2D(scrolledOriginX + getKnobXForPosition(position) - knobWidth * 0.5f, originY, 0f, knobWidth, knob
392                         .getHeight());
393             }
394         } else {
395             if (view.bind(knob)) {
396                 final float knobWidth = knob.getWidth();
397                 final float knobHeight = knob.getHeight();
398                 view.draw2D(scrolledOriginX + getKnobXForPosition(position) - knobWidth * 0.5f, view.getHeight() - knobHeight, 0f,
399                         knobWidth, knobHeight);
400             }
401             // we draw the current time on top of the knob
402             if (mInDrag || mAnimTextAlpha != 0.0f) {
403                 Marker anchor = getAnchorMarker();
404                 if (anchor != null) {
405                     Texture month = mMonthLabels[anchor.month];
406                     Texture day = mOpaqueDayLabels[anchor.day];
407                     Texture year = getYearLabel(anchor.year);
408                     boolean validDate = true;
409                     if (anchor.year <= 1970) {
410                         month = mDateUnknown;
411                         day = null;
412                         year = null;
413                         validDate = false;
414                     }
415                     view.loadTexture(month);
416                     if (validDate) {
417                         view.loadTexture(day);
418                         view.loadTexture(year);
419                     }
420                     int numPixelsBufferX = 70;
421                     float expectedWidth = month.getWidth()
422                             + ((validDate) ? (day.getWidth() + year.getWidth() + 10 * Gallery.PIXEL_DENSITY) : 0);
423                     if ((expectedWidth + numPixelsBufferX * Gallery.PIXEL_DENSITY) != mBackgroundRect.right) {
424                         mBackgroundRect.right = (int) (expectedWidth + numPixelsBufferX * Gallery.PIXEL_DENSITY);
425                         mBackgroundRect.bottom = (int) (month.getHeight() + 20 * Gallery.PIXEL_DENSITY);
426                         try {
427                             Bitmap bitmap = Bitmap.createBitmap(mBackgroundRect.right, mBackgroundRect.bottom,
428                                     Bitmap.Config.ARGB_8888);
429                             Canvas canvas = new Canvas();
430                             canvas.setBitmap(bitmap);
431                             mBackground.draw(canvas, mBackgroundRect, SRC_PAINT);
432                             mBackgroundTexture = new BitmapTexture(bitmap);
433                             view.loadTexture(mBackgroundTexture);
434                             bitmap.recycle();
435                         } catch (OutOfMemoryError e) {
436                             // Do nothing.
437                         }
438                     }
439                     gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE);
440                     gl.glColor4f(mAnimTextAlpha, mAnimTextAlpha, mAnimTextAlpha, mAnimTextAlpha);
441                     float x = (view.getWidth() - expectedWidth - numPixelsBufferX * Gallery.PIXEL_DENSITY) / 2;
442                     float y = (view.getHeight() - 10 * Gallery.PIXEL_DENSITY) * 0.5f;
443                     if (mBackgroundTexture != null) {
444                         view.draw2D(mBackgroundTexture, x, y);
445                     }
446                     y = view.getHeight() * 0.5f;
447                     x = (view.getWidth() - expectedWidth) / 2;
448                     view.draw2D(month, x, y);
449                     if (validDate) {
450                         x += month.getWidth() + 3 * Gallery.PIXEL_DENSITY;
451                         view.draw2D(day, x, y);
452                         x += day.getWidth() + 7 * Gallery.PIXEL_DENSITY;
453                         view.draw2D(year, x, y);
454                     }
455                     if (mAnimTextAlpha != 1f) {
456                         gl.glColor4f(1f, 1f, 1f, 1f);
457                     }
458                     gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_REPLACE);
459                 }
460             }
461         }
462     }
463
464     @Override
465     public boolean onTouchEvent(MotionEvent event) {
466         // Set position on touch movement.
467         mDragX = event.getX();
468         mPosition = getPositionForKnobX(mDragX + mScroll);
469
470         // Notify the listener.
471         if (mListener != null) {
472             mListener.onTimeChanged(this);
473         }
474
475         // Update state when touch begins and ends.
476         switch (event.getAction()) {
477         case MotionEvent.ACTION_DOWN:
478             mInDrag = true;
479             break;
480         case MotionEvent.ACTION_UP:
481         case MotionEvent.ACTION_CANCEL:
482             // mScroll = getScrollForPosition(mPosition);
483             mInDrag = false;
484
485             // Clamp to the nearest marker.
486             setItem(getItem());
487         default:
488             break;
489         }
490
491         return true;
492     }
493
494     public void onFeedChanged(MediaFeed feed, boolean needsLayout) {
495         layout();
496     }
497
498     private static final class Marker {
499         Marker(StringTexture texture, float x, long time, int year, int month, int day, int type, int expectedCapacity) {
500             this.x = x;
501             this.year = year;
502             this.month = month;
503             this.day = day;
504             this.items = new ArrayList<MediaItem>(expectedCapacity);
505         }
506
507         public static final int TYPE_MONTH = 1;
508         public static final int TYPE_DAY = 2;
509         public static final int TYPE_DOT = 3;
510         public ArrayList<MediaItem> items;
511         public final float x;
512         public final int year;
513         public final int month;
514         public final int day;
515     }
516
517     @Override
518     public void generate(RenderView view, Lists lists) {
519         lists.updateList.add(this);
520         lists.blendedList.add(this);
521         lists.hitTestList.add(this);
522     }
523
524     public void onFeedAboutToChange(MediaFeed feed) {
525         // nothing needs to be done
526         return;
527     }
528
529     private StringTexture getYearLabel(int year) {
530         if (year <= 1970)
531             return mDot;
532         StringTexture label = mYearLabels.get(year);
533         if (label == null) {
534             label = new StringTexture(Integer.toString(year), mMonthYearFormat);
535             mYearLabels.put(year, label);
536         }
537         return label;
538     }
539
540     public boolean isDragged() {
541         return mInDrag;
542     }
543 }