blob: 1fc42382dcbeb52ec24ef0ca2bcbe5809da7b280 [file] [log] [blame]
package com.cooliris.media;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import javax.microedition.khronos.opengles.GL11;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.NinePatch;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.util.SparseArray;
import android.view.MotionEvent;
import com.cooliris.media.RenderView.Lists;
public final class TimeBar extends Layer implements MediaFeed.Listener {
public static final int HEIGHT = 48;
private static final int MARKER_SPACING_PIXELS = 50;
private static final float AUTO_SCROLL_MARGIN = 100f;
private static final Paint SRC_PAINT = new Paint();
private Listener mListener = null;
private MediaFeed mFeed = null;
private float mTotalWidth = 0f;
private float mPosition = 0f;
private float mPositionAnim = 0f;
private float mScroll = 0f;
private float mScrollAnim = 0f;
private boolean mInDrag = false;
private float mDragX = 0f;
private ArrayList<Marker> mMarkers = new ArrayList<Marker>();
private ArrayList<Marker> mMarkersCopy = new ArrayList<Marker>();
private static final int KNOB = R.drawable.scroller_new;
private static final int KNOB_PRESSED = R.drawable.scroller_pressed_new;
private final StringTexture.Config mMonthYearFormat = new StringTexture.Config();
private final StringTexture.Config mDayFormat = new StringTexture.Config();
private final SparseArray<StringTexture> mYearLabels = new SparseArray<StringTexture>();
private StringTexture mDateUnknown;
private final StringTexture[] mMonthLabels = new StringTexture[12];
private final StringTexture[] mDayLabels = new StringTexture[32];
private final StringTexture[] mOpaqueDayLabels = new StringTexture[32];
private final StringTexture mDot = new StringTexture("¥");
private final HashMap<MediaItem, Marker> mTracker = new HashMap<MediaItem, Marker>(1024);
private int mState;
private float mTextAlpha = 0.0f;
private float mAnimTextAlpha = 0.0f;
private boolean mShowTime;
private NinePatch mBackground;
private Rect mBackgroundRect;
private BitmapTexture mBackgroundTexture;
public interface Listener {
public void onTimeChanged(TimeBar timebar);
}
TimeBar(Context context) {
// Setup formatting for text labels.
mMonthYearFormat.fontSize = 17f * Gallery.PIXEL_DENSITY;
mMonthYearFormat.bold = true;
mMonthYearFormat.a = 0.85f;
mDayFormat.fontSize = 17f * Gallery.PIXEL_DENSITY;
mDayFormat.a = 0.61f;
regenerateStringsForContext(context);
Bitmap background = BitmapFactory.decodeResource(context.getResources(), R.drawable.popup);
mBackground = new NinePatch(background, background.getNinePatchChunk(), null);
mBackgroundRect = new Rect();
SRC_PAINT.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
}
public void regenerateStringsForContext(Context context) {
// Create textures for month names.
String[] months = context.getResources().getStringArray(R.array.months_abbreviated);
for (int i = 0; i < months.length; ++i) {
mMonthLabels[i] = new StringTexture(months[i], mMonthYearFormat);
}
for (int i = 0; i <= 31; ++i) {
mDayLabels[i] = new StringTexture(Integer.toString(i), mDayFormat);
mOpaqueDayLabels[i] = new StringTexture(Integer.toString(i), mMonthYearFormat);
}
mDateUnknown = new StringTexture(context.getResources().getString(R.string.date_unknown), mMonthYearFormat);
mBackgroundTexture = null;
}
public void setListener(Listener listener) {
mListener = listener;
}
public void setFeed(MediaFeed feed, int state, boolean needsLayout) {
mFeed = feed;
mState = state;
layout();
if (needsLayout) {
mPosition = 0;
mScroll = getScrollForPosition(mPosition);
}
}
@Override
protected void onSizeChanged() {
mScroll = getScrollForPosition(mPosition);
}
public MediaItem getItem() {
synchronized (mMarkers) {
// x is between 0 and 1.0f
int numMarkers = mMarkers.size();
if (numMarkers == 0)
return null;
int index = (int) (mPosition * (numMarkers));
if (index >= numMarkers)
index = numMarkers - 1;
Marker marker = mMarkers.get(index);
if (marker != null) {
// we have to find the index of the media item depending upon
// the value of mPosition
float deltaBetweenMarkers = 1.0f / numMarkers;
float increment = mPosition - index * deltaBetweenMarkers;
// if (increment > deltaBetweenMarkers)
// increment = deltaBetweenMarkers;
// if (increment < 0)
// increment = 0;
ArrayList<MediaItem> items = marker.items;
int numItems = items.size();
if (numItems == 0)
return null;
int itemIndex = (int) ((numItems) * increment / deltaBetweenMarkers);
if (itemIndex >= numItems)
itemIndex = numItems - 1;
return marker.items.get(itemIndex);
}
}
return null;
}
private Marker getAnchorMarker() {
synchronized (mMarkers) {
// x is between 0 and 1.0f
int numMarkers = mMarkers.size();
if (numMarkers == 0)
return null;
int index = (int) (mPosition * (numMarkers));
if (index >= numMarkers)
index = numMarkers - 1;
Marker marker = mMarkers.get(index);
return marker;
}
}
public void setItem(MediaItem item) {
Marker marker = mTracker.get(item);
if (marker != null) {
float markerX = (mTotalWidth == 0.0f) ? 0.0f : marker.x / mTotalWidth;
mPosition = Math.max(0.0f, Math.min(1.0f, markerX));
mScroll = getScrollForPosition(mPosition);
}
}
private void layout() {
if (mFeed != null) {
// Clear existing markers.
mTracker.clear();
synchronized (mMarkers) {
mMarkers.clear();
}
float scrollX = mScroll;
// Place markers for every time interval that intersects one of the
// clusters.
// Markers for a full month would be for example: Jan 5 10 15 20 25
// 30.
MediaFeed feed = mFeed;
int lastYear = -1;
int lastMonth = -1;
int lastDayBlock = -1;
float dx = 0f;
int increment = 12;
MediaSet set = null;
mShowTime = true;
if (mState == GridLayer.STATE_GRID_VIEW) {
set = feed.getFilteredSet();
if (set == null) {
set = feed.getCurrentSet();
}
} else {
increment = 2;
if (!feed.hasExpandedMediaSet()) {
mShowTime = false;
}
set = new MediaSet();
int numSlots = feed.getNumSlots();
for (int i = 0; i < numSlots; ++i) {
MediaSet slotSet = feed.getSetForSlot(i);
if (slotSet != null) {
ArrayList<MediaItem> slotSetItems = slotSet.getItems();
if (slotSetItems != null && slotSet.getNumItems() > 0) {
MediaItem item = slotSetItems.get(0);
if (item != null) {
set.addItem(item);
}
}
}
}
}
if (set != null) {
GregorianCalendar time = new GregorianCalendar();
ArrayList<MediaItem> items = set.getItems();
if (items != null) {
int j = 0;
while (j < set.getNumItems()) {
final MediaItem item = items.get(j);
if (item == null)
continue;
time.setTimeInMillis(item.mDateTakenInMs);
// Detect year rollovers.
final int year = time.get(Calendar.YEAR);
if (year != lastYear) {
lastYear = year;
lastMonth = -1;
lastDayBlock = -1;
}
Marker marker = null;
// Detect month rollovers and emit a month marker.
final int month = time.get(Calendar.MONTH);
final int dayBlock = time.get(Calendar.DATE);
if (month != lastMonth) {
lastMonth = month;
lastDayBlock = -1;
marker = new Marker(dx, time.getTimeInMillis(), year, month, dayBlock,
Marker.TYPE_MONTH, increment);
dx = addMarker(marker);
} else if (dayBlock != lastDayBlock) {
lastDayBlock = dayBlock;
if (dayBlock != 0) {
marker = new Marker(dx, time.getTimeInMillis(), year, month, dayBlock,
Marker.TYPE_DAY, increment);
dx = addMarker(marker);
}
} else {
marker = new Marker(dx, time.getTimeInMillis(), year, month, dayBlock, Marker.TYPE_DOT, increment);
dx = addMarker(marker);
}
for (int k = 0; k < increment; ++k) {
int index = k + j;
if (index < 0)
continue;
if (index >= items.size())
break;
if (index == items.size() - 1 && k != 0)
break;
MediaItem thisItem = items.get(index);
marker.items.add(thisItem);
mTracker.put(thisItem, marker);
}
if (j == items.size() - 1)
break;
j += increment;
if (j >= items.size() - 1)
j = items.size() - 1;
}
}
mTotalWidth = dx - MARKER_SPACING_PIXELS * Gallery.PIXEL_DENSITY;
}
mPosition = getPositionForScroll(scrollX);
mPositionAnim = mPosition;
synchronized (mMarkersCopy) {
int numMarkers = mMarkers.size();
mMarkersCopy.clear();
mMarkersCopy.ensureCapacity(numMarkers);
for (int i = 0; i < numMarkers; ++i) {
mMarkersCopy.add(mMarkers.get(i));
}
}
}
}
private float addMarker(Marker marker) {
mMarkers.add(marker);
return marker.x + MARKER_SPACING_PIXELS * Gallery.PIXEL_DENSITY;
}
/*
* private float getKnobXForPosition(float position) { return position * (mTotalWidth - mKnob.getWidth()); }
*
* private float getPositionForKnobX(float knobX) { return Math.max(0f, Math.min(1f, knobX / (mTotalWidth - mKnob.getWidth())));
* }
*
* private float getScrollForPosition(float position) { return position * (mTotalWidth - mWidth);// - (1f - 2f * position) *
* MARKER_SPACING_PIXELS; }
*/
private float getScrollForPosition(float position) {
// Map position [0, 1] to scroll [-visibleWidth/2, totalWidth -
// visibleWidth/2].
// This has the effect of centering the scroll knob on screen.
float halfWidth = mWidth * 0.5f;
float positionInv = 1f - position;
float centered = positionInv * -halfWidth + position * (mTotalWidth - halfWidth);
return centered;
}
private float getPositionForScroll(float scroll) {
float halfWidth = mWidth * 0.5f;
if (mTotalWidth == 0)
return 0;
return ((scroll + halfWidth) / (mTotalWidth));
}
private float getKnobXForPosition(float position) {
return position * mTotalWidth;
}
private float getPositionForKnobX(float knobX) {
float normKnobX = (mTotalWidth == 0) ? 0 : knobX / mTotalWidth;
return Math.max(0f, Math.min(1f, normKnobX));
}
@Override
public boolean update(RenderView view, float dt) {
// Update animations.
final float ratio = Math.min(1f, 10f * dt);
final float invRatio = 1f - ratio;
mPositionAnim = ratio * mPosition + invRatio * mPositionAnim;
mScrollAnim = ratio * mScroll + invRatio * mScrollAnim;
// Handle autoscroll.
if (mInDrag) {
final float x = getKnobXForPosition(mPosition) - mScrollAnim;
float velocity;
float autoScrollMargin = AUTO_SCROLL_MARGIN * Gallery.PIXEL_DENSITY;
if (x < autoScrollMargin) {
velocity = -(float) Math.pow((1f - x / autoScrollMargin), 2);
} else if (x > mWidth - autoScrollMargin) {
velocity = (float) Math.pow(1f - (mWidth - x) / autoScrollMargin, 2);
} else {
velocity = 0;
}
mScroll += velocity * 400f * dt;
mPosition = getPositionForKnobX(mDragX + mScroll);
mTextAlpha = 1.0f;
} else {
mTextAlpha = 0.0f;
}
mAnimTextAlpha = FloatUtils.animate(mAnimTextAlpha, mTextAlpha, dt);
return mAnimTextAlpha != mTextAlpha;
}
@Override
public void renderBlended(RenderView view, GL11 gl) {
final float originX = mX;
final float originY = mY;
final float scrollOffset = mScrollAnim;
final float scrolledOriginX = originX - scrollOffset;
final float position = mPositionAnim;
final int knobId = mInDrag ? KNOB_PRESSED : KNOB;
final Texture knob = view.getResource(knobId);
// Draw the scroller knob.
if (!mShowTime) {
if (view.bind(knob)) {
final float knobWidth = knob.getWidth();
view.draw2D(scrolledOriginX + getKnobXForPosition(position) - knobWidth * 0.5f, originY, 0f, knobWidth, knob
.getHeight());
}
} else {
if (view.bind(knob)) {
final float knobWidth = knob.getWidth();
final float knobHeight = knob.getHeight();
view.draw2D(scrolledOriginX + getKnobXForPosition(position) - knobWidth * 0.5f, view.getHeight() - knobHeight, 0f,
knobWidth, knobHeight);
}
// we draw the current time on top of the knob
if (mInDrag || mAnimTextAlpha != 0.0f) {
Marker anchor = getAnchorMarker();
if (anchor != null) {
Texture month = mMonthLabels[anchor.month];
Texture day = mOpaqueDayLabels[anchor.day];
Texture year = getYearLabel(anchor.year);
boolean validDate = true;
if (anchor.year <= 1970) {
month = mDateUnknown;
day = null;
year = null;
validDate = false;
}
view.loadTexture(month);
if (validDate) {
view.loadTexture(day);
view.loadTexture(year);
}
int numPixelsBufferX = 70;
float expectedWidth = month.getWidth()
+ ((validDate) ? (day.getWidth() + year.getWidth() + 10 * Gallery.PIXEL_DENSITY) : 0);
if ((expectedWidth + numPixelsBufferX * Gallery.PIXEL_DENSITY) != mBackgroundRect.right) {
mBackgroundRect.right = (int) (expectedWidth + numPixelsBufferX * Gallery.PIXEL_DENSITY);
mBackgroundRect.bottom = (int) (month.getHeight() + 20 * Gallery.PIXEL_DENSITY);
try {
Bitmap bitmap = Bitmap.createBitmap(mBackgroundRect.right, mBackgroundRect.bottom,
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas();
canvas.setBitmap(bitmap);
mBackground.draw(canvas, mBackgroundRect, SRC_PAINT);
mBackgroundTexture = new BitmapTexture(bitmap);
view.loadTexture(mBackgroundTexture);
bitmap.recycle();
} catch (OutOfMemoryError e) {
// Do nothing.
}
}
gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE);
gl.glColor4f(mAnimTextAlpha, mAnimTextAlpha, mAnimTextAlpha, mAnimTextAlpha);
float x = (view.getWidth() - expectedWidth - numPixelsBufferX * Gallery.PIXEL_DENSITY) / 2;
float y = (view.getHeight() - 10 * Gallery.PIXEL_DENSITY) * 0.5f;
if (mBackgroundTexture != null) {
view.draw2D(mBackgroundTexture, x, y);
}
y = view.getHeight() * 0.5f;
x = (view.getWidth() - expectedWidth) / 2;
view.draw2D(month, x, y);
if (validDate) {
x += month.getWidth() + 3 * Gallery.PIXEL_DENSITY;
view.draw2D(day, x, y);
x += day.getWidth() + 7 * Gallery.PIXEL_DENSITY;
view.draw2D(year, x, y);
}
if (mAnimTextAlpha != 1f) {
gl.glColor4f(1f, 1f, 1f, 1f);
}
gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_REPLACE);
}
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// Set position on touch movement.
mDragX = event.getX();
mPosition = getPositionForKnobX(mDragX + mScroll);
// Notify the listener.
if (mListener != null) {
mListener.onTimeChanged(this);
}
// Update state when touch begins and ends.
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mInDrag = true;
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
// mScroll = getScrollForPosition(mPosition);
mInDrag = false;
// Clamp to the nearest marker.
setItem(getItem());
default:
break;
}
return true;
}
public void onFeedChanged(MediaFeed feed, boolean needsLayout) {
layout();
}
private static final class Marker {
Marker(float x, long time, int year, int month, int day, int type, int expectedCapacity) {
this.x = x;
this.year = year;
this.month = month;
this.day = day;
this.items = new ArrayList<MediaItem>(expectedCapacity);
}
public static final int TYPE_MONTH = 1;
public static final int TYPE_DAY = 2;
public static final int TYPE_DOT = 3;
public ArrayList<MediaItem> items;
public final float x;
public final int year;
public final int month;
public final int day;
}
@Override
public void generate(RenderView view, Lists lists) {
lists.updateList.add(this);
lists.blendedList.add(this);
lists.hitTestList.add(this);
}
public void onFeedAboutToChange(MediaFeed feed) {
// nothing needs to be done
return;
}
private StringTexture getYearLabel(int year) {
if (year <= 1970)
return mDot;
StringTexture label = mYearLabels.get(year);
if (label == null) {
label = new StringTexture(Integer.toString(year), mMonthYearFormat);
mYearLabels.put(year, label);
}
return label;
}
public boolean isDragged() {
return mInDrag;
}
}