blob: 29c73f5109dbdc6d313fccb2798f8dab6c385067 [file] [log] [blame]
package com.android.gallery3d.ui;
import static com.android.gallery3d.ui.Util.dpToPixel;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.MotionEvent;
import com.android.gallery3d.R;
import com.android.gallery3d.anim.IntAnimation;
import java.util.ArrayList;
import java.util.NoSuchElementException;
public class Pathbar extends GLView {
public static final int INDEX_NONE = -1;
private static final int FONT_COLOR = Color.WHITE;
private static final int FONT_SIZE = 18;
private static final int PROGRESS_STEP_COUNT = 8;
private static final float SHOW_TITLE_THRESHOLD = 0.9f;
private static final int PUSH_ANIMATION = 1;
private static final int POP_ANIMATION = 2;
private static final int NO_ANIMATION = 0;
private static final int HORIZONTAL_PADDING = 3;
private static final int VERTICAL_PADDING = 0;
private static final int UPDATE_PROGRESS = 1;
private int mAnimationType = NO_ANIMATION;
private static int sHorizontalPadding = -1;
private static int sVerticalPadding;
private static int sFontSize;
private final ArrayList<Item> mCrumb = new ArrayList<Item>();
private IntAnimation mAnim;
private final NinePatchTexture mPathbar;
private final NinePatchTexture mPathcap;
private final NinePatchTexture mPathbarPressed;
private final NinePatchTexture mPathcapPressed;
private final BasicTexture mProgressIcon;
private int mProgressStep = 0;
private boolean mProgressUpdated = false;
private Handler mHandler;
private boolean mProgressActivate = false;
private int mOffsetX;
private boolean mRequestRender;
private boolean mPressedItemChanged;
private OnClickedListener mOnClickedListener;
private int mPressedIndex = INDEX_NONE;
public static interface OnClickedListener {
public void onClicked(Pathbar source, int index);
}
private static void initializeStaticVariables(Context context) {
if (sHorizontalPadding >= 0) return;
sHorizontalPadding = dpToPixel(context, HORIZONTAL_PADDING);
sVerticalPadding = dpToPixel(context, VERTICAL_PADDING);
sFontSize = dpToPixel(context, FONT_SIZE);
}
public Pathbar(Context context) {
initializeStaticVariables(context);
mProgressIcon = new ResourceTexture(context, R.drawable.ic_spinner);
mPathbar = new NinePatchTexture(context, R.drawable.pathbar);
mPathcap = new NinePatchTexture(context, R.drawable.pathcap);
mPathbarPressed = new NinePatchTexture(context, R.drawable.pathbar_pressed);
mPathcapPressed = new NinePatchTexture(context, R.drawable.pathcap_pressed);
}
public void setOnClickedListener(OnClickedListener listener) {
mOnClickedListener = listener;
}
public Item get(int index) {
return mCrumb.get(index);
}
public int size() {
return mCrumb.size();
}
private void setPressedItem(int index) {
if (mPressedIndex == index) return;
Log.v("Pathbar", "index change from " + mPressedIndex + " to " + index);
mPressedIndex = index;
mPressedItemChanged = true;
invalidate();
}
@Override
protected boolean onTouch(MotionEvent event) {
int x = (int) event.getX();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
setPressedItem(getItemAtPosition(x));
mPressedItemChanged = false;
break;
case MotionEvent.ACTION_MOVE:
setPressedItem(getItemAtPosition(x));
break;
case MotionEvent.ACTION_UP:
if (!mPressedItemChanged && mOnClickedListener != null) {
mOnClickedListener.onClicked(this, mPressedIndex);
}
setPressedItem(INDEX_NONE);
}
return true;
}
private int getItemAtPosition(int x) {
int xoffset = 0;
int size = mCrumb.size();
int padding = mPathbar.getPaddings().right;
for (int i = 0, n = size - 1; i < n; ++i) {
xoffset += getItemWidthWithoutTitle(mCrumb.get(i)) + padding;
if (xoffset > x) return i;
}
if (size > 0) {
int i = size - 1;
xoffset += getItemWidth(mCrumb.get(i)) + padding;
if (xoffset > x) return i;
}
return INDEX_NONE;
}
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
int height = mProgressIcon.getHeight();
for (Item item : mCrumb) {
height = Math.max(height, item.mIcon.getHeight());
height = Math.max(height, item.mTitle.getHeight());
}
height += sVerticalPadding * 2;
MeasureHelper.getInstance(this)
.setPreferredContentSize(Integer.MAX_VALUE, height)
.measure(widthSpec, heightSpec);
}
private StringTexture getStringTexture(String text) {
return StringTexture.newInstance(text, sFontSize, FONT_COLOR);
}
@Override
protected void onAttachToRoot(GLRootView root) {
super.onAttachToRoot(root);
mHandler = new SynchronizedHandler(root) {
@Override
public void handleMessage(Message message) {
switch (message.what) {
case UPDATE_PROGRESS:
if (++mProgressStep == PROGRESS_STEP_COUNT) {
mProgressStep = 0;
}
mProgressUpdated = false;
invalidate();
}
}
};
}
public void push(BasicTexture icon, String title) {
mCrumb.add(new Item(icon, getStringTexture(title)));
if (mCrumb.size() > 1) {
Item item = mCrumb.get(mCrumb.size() - 2);
mAnim = new IntAnimation(
getItemWidth(item), getItemWidthWithoutTitle(item), 300);
mAnim.start();
mAnimationType = PUSH_ANIMATION;
}
invalidate();
}
public Item pop() {
if (mCrumb.isEmpty()) throw new NoSuchElementException();
Item result = mCrumb.remove(mCrumb.size() - 1);
if (mCrumb.size() > 0) {
Item item = mCrumb.get(mCrumb.size() - 1);
mAnim = new IntAnimation(
getItemWidthWithoutTitle(item), getItemWidth(item), 300);
mAnim.start();
mAnimationType = POP_ANIMATION;
}
invalidate();
return result;
}
public void pop(int number) {
if (mCrumb.size() < number) throw new IllegalArgumentException();
int index = mCrumb.size() - 1;
for (int i = 0; i < number; ++i) {
mCrumb.remove(index - i);
}
if (mCrumb.size() > 0) {
Item item = mCrumb.get(mCrumb.size() - 1);
mAnim = new IntAnimation(
getItemWidthWithoutTitle(item), getItemWidth(item), 300);
mAnim.start();
mAnimationType = POP_ANIMATION;
}
invalidate();
}
public Item getLast() {
if (mCrumb.isEmpty()) throw new NoSuchElementException();
return mCrumb.get(mCrumb.size() - 1);
}
private static int getItemWidth(Item item) {
return sHorizontalPadding * 3
+ item.mIcon.getWidth() + item.mTitle.getWidth();
}
private static int getItemWidthWithoutTitle(Item item) {
return sHorizontalPadding * 2 + item.mIcon.getWidth();
}
private void renderItem(GLCanvas canvas, BasicTexture icon,
StringTexture title, NinePatchTexture background, int width) {
int height = getHeight();
int offsetX = mOffsetX;
Rect p = background.getPaddings();
background.draw(
canvas, offsetX - p.left, 0, width + p.left + p.right, height);
int offsetY = (height - icon.getHeight()) / 2;
offsetX += sHorizontalPadding;
if (icon != mProgressIcon) {
icon.draw(canvas, offsetX, offsetY);
} else {
int degrees = mProgressStep * 360 / PROGRESS_STEP_COUNT;
int pivotX = offsetX + icon.getWidth() / 2;
int pivotY = offsetY + icon.getHeight() / 2;
canvas.save(GLCanvas.SAVE_FLAG_MATRIX);
canvas.translate(pivotX, pivotY, 0);
canvas.rotate(degrees, 0, 0, 1);
canvas.translate(-pivotX, -pivotY, 0);
icon.draw(canvas, offsetX, offsetY);
canvas.restore();
if (!mProgressUpdated) {
mProgressUpdated = true;
mHandler.sendEmptyMessageDelayed(UPDATE_PROGRESS, 125);
}
}
offsetX += icon.getWidth();
if (title != null) {
offsetY = (height - title.getHeight()) / 2;
title.draw(canvas, offsetX, offsetY);
}
mOffsetX += width + p.right;
}
private void renderItemWithAnimation(GLCanvas canvas, BasicTexture icon,
StringTexture title, NinePatchTexture background) {
boolean moreAnim = mAnim.calculate(canvas.currentAnimationTimeMillis());
int width = mAnim.get();
if (width < (3 * sHorizontalPadding
+ icon.getWidth() + title.getWidth()) * SHOW_TITLE_THRESHOLD) {
title = null;
}
if (!moreAnim) {
mAnimationType = NO_ANIMATION;
mAnim = null;
} else {
mRequestRender = true;
}
renderItem(canvas, icon, title, background, width);
}
@Override
protected void render(GLCanvas canvas) {
mRequestRender = false;
mOffsetX = 0;
int size = mCrumb.size();
for (int i = 0, n = size - 2; i < n; ++i) {
Item item = mCrumb.get(i);
int width = getItemWidthWithoutTitle(item);
renderItem(canvas, item.mIcon, null,
i == mPressedIndex ? mPathbarPressed : mPathbar, width);
}
// render the 2nd last item
if (size >= 2) {
int index = size - 2;
Item item = mCrumb.get(index);
NinePatchTexture bg =
index == mPressedIndex ? mPathbarPressed : mPathbar;
if (mAnimationType == PUSH_ANIMATION) {
renderItemWithAnimation(canvas, item.mIcon, item.mTitle, bg);
} else {
renderItem(canvas, item.mIcon,
null, bg, getItemWidthWithoutTitle(item));
}
}
// render the last item
if (size >= 1) {
int index = size - 1;
Item item = mCrumb.get(index);
BasicTexture icon = mProgressActivate ? mProgressIcon : item.mIcon;
NinePatchTexture bg =
index == mPressedIndex ? mPathcapPressed : mPathcap;
if (mAnimationType == POP_ANIMATION) {
renderItemWithAnimation(canvas, icon, item.mTitle, bg);
} else {
renderItem(canvas, icon, item.mTitle, bg, getItemWidth(item));
}
}
if (mRequestRender) invalidate();
}
public void startProgressAnimation() {
if (mProgressActivate) return;
mProgressActivate = true;
mHandler.sendEmptyMessageDelayed(UPDATE_PROGRESS, 125);
invalidate();
}
public void stopProgressAnimation() {
if (!mProgressActivate) return;
mProgressActivate = false;
mHandler.removeMessages(UPDATE_PROGRESS);
invalidate();
}
public class Item {
private BasicTexture mIcon;
private StringTexture mTitle;
Item(BasicTexture icon, StringTexture title) {
mIcon = icon;
mTitle = title;
}
public void setIcon(BasicTexture icon) {
if (icon == mIcon) return;
mIcon = icon;
invalidate();
}
public void setTitle(String title) {
mTitle = getStringTexture(title);
invalidate();
}
}
}