OSDN Git Service

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