OSDN Git Service

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