1 package com.cooliris.media;
3 import javax.microedition.khronos.opengles.GL11;
5 import com.cooliris.media.R;
7 import android.content.Context;
8 import android.content.res.Resources;
9 import android.graphics.Bitmap;
10 import android.graphics.BitmapFactory;
11 import android.graphics.Canvas;
12 import android.graphics.NinePatch;
13 import android.graphics.Paint;
14 import android.graphics.PorterDuff;
15 import android.graphics.PorterDuffXfermode;
16 import android.graphics.Rect;
17 import android.graphics.drawable.Drawable;
18 import android.os.SystemClock;
19 import android.text.TextPaint;
20 import android.view.MotionEvent;
22 public final class PopupMenu extends Layer {
23 private static final int POPUP_TRIANGLE_EXTRA_HEIGHT = 14;
24 private static final int POPUP_TRIANGLE_X_MARGIN = 16;
25 private static final int POPUP_Y_OFFSET = 20;
26 private static final Paint SRC_PAINT = new Paint();
27 private static final int PADDING_LEFT = 10 + 5;
28 private static final int PADDING_TOP = 10 + 3;
29 private static final int PADDING_RIGHT = 10 + 5;
30 private static final int PADDING_BOTTOM = 30 + 10;
31 private static final int ICON_TITLE_MIN_WIDTH = 100;
32 private static final IconTitleDrawable.Config ICON_TITLE_CONFIG;
34 private PopupTexture mPopupTexture;
35 private Listener mListener = null;
36 private Option[] mOptions = {};
37 private boolean mNeedsLayout = false;
38 private boolean mShow = false;
39 private final FloatAnim mShowAnim = new FloatAnim(0f);
40 private int mRowHeight = 36;
41 private int mSelectedItem = -1;
44 TextPaint paint = new TextPaint();
45 paint.setTextSize(17f * Gallery.PIXEL_DENSITY);
46 paint.setColor(0xffffffff);
47 paint.setAntiAlias(true);
48 ICON_TITLE_CONFIG = new IconTitleDrawable.Config((int) (45 * Gallery.PIXEL_DENSITY), (int) (34 * Gallery.PIXEL_DENSITY),
50 SRC_PAINT.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
53 public PopupMenu(Context context) {
54 mPopupTexture = new PopupTexture(context);
58 public void setListener(Listener listener) {
62 public void setOptions(Option[] options) {
68 public void showAtPoint(int pointX, int pointY, int outerWidth, int outerHeight) {
69 // Compute layout if needed.
73 // Try to center the popup over the target point.
74 int width = (int) mWidth;
75 int height = (int) mHeight;
76 int widthOver2 = width / 2;
77 int x = pointX - widthOver2;
78 int y = pointY + POPUP_Y_OFFSET - height;
79 int clampedX = Shared.clamp(x, 0, outerWidth - width);
80 int triangleWidthOver2 = mPopupTexture.mTriangleBottom.getWidth() / 2;
81 mPopupTexture.mTriangleX = Shared.clamp(widthOver2 + (x - clampedX) - triangleWidthOver2, POPUP_TRIANGLE_X_MARGIN, width
82 - POPUP_TRIANGLE_X_MARGIN * 2);
83 mPopupTexture.setNeedsDraw();
84 setPosition(clampedX, y);
86 // Fade in the menu if it is not already visible, otherwise snap to the new location.
90 mShowAnim.setValue(0);
91 mShowAnim.animateValue(1f, 0.4f, SystemClock.uptimeMillis());
95 public void close(boolean fadeOut) {
98 mShowAnim.animateValue(0, 0.3f, SystemClock.uptimeMillis());
100 mShowAnim.setValue(0);
109 public void generate(RenderView view, RenderView.Lists lists) {
110 lists.blendedList.add(this);
111 lists.hitTestList.add(this);
112 lists.systemList.add(this);
113 lists.updateList.add(this);
117 protected void onSizeChanged() {
118 super.onSizeChanged();
119 mPopupTexture.setSize((int) mWidth, (int) mHeight);
123 protected void onSurfaceCreated(RenderView view, GL11 gl) {
128 public boolean onTouchEvent(MotionEvent event) {
129 int hit = hitTestOptions((int) event.getX(), (int) event.getY());
130 switch (event.getAction()) {
131 case MotionEvent.ACTION_DOWN:
132 case MotionEvent.ACTION_MOVE:
133 setSelectedItem(hit);
135 case MotionEvent.ACTION_UP:
136 if (hit != -1 && mSelectedItem == hit) {
137 mOptions[hit].mAction.run();
138 if (mListener != null) {
139 mListener.onSelectionClicked(this, hit);
142 case MotionEvent.ACTION_CANCEL:
149 private void setSelectedItem(int hit) {
150 if (mSelectedItem != hit) {
152 mPopupTexture.setNeedsDraw();
153 if (mListener != null) {
154 mListener.onSelectionChanged(this, hit);
160 public boolean update(RenderView view, float timeElapsed) {
161 return (mShowAnim.getTimeRemaining(SystemClock.uptimeMillis()) > 0);
165 public void renderBlended(RenderView view, GL11 gl) {
166 // Hide the layer if the close animation is complete.
167 float showRatio = mShowAnim.getValue(SystemClock.uptimeMillis());
168 boolean show = mShow;
169 if (showRatio < 0.003f && !show) {
173 // Draw the selection menu with the show animation.
176 if (show && showRatio < 1f) {
177 // Animate the scale as well for the open animation.
180 if (showRatio < split) {
181 scale = 0.8f + 0.3f * showRatio / split;
183 scale = 1f + ((1f - showRatio) / (1f - split)) * 0.1f;
185 mPopupTexture.drawWithEffect(view, gl, x, y, 0.5f, 0.65f, showRatio, scale);
187 if (showRatio < 1f) {
188 view.setAlpha(showRatio);
190 mPopupTexture.draw(view, gl, x, y);
191 if (showRatio < 1f) {
198 private void layout() {
199 // Mark as not needing layout.
200 mNeedsLayout = false;
202 // Measure the menu options.
203 Option[] options = mOptions;
204 int numOptions = options.length;
205 int maxWidth = (int) (ICON_TITLE_MIN_WIDTH * Gallery.PIXEL_DENSITY);
206 for (int i = 0; i != numOptions; ++i) {
207 Option option = options[i];
208 IconTitleDrawable drawable = option.mDrawable;
209 if (drawable == null) {
210 drawable = new IconTitleDrawable(option.mTitle, option.mIcon, ICON_TITLE_CONFIG);
211 option.mDrawable = drawable;
213 int width = drawable.getIntrinsicWidth();
214 if (width > maxWidth) {
219 // Layout the menu options.
220 int rowHeight = (int) (mRowHeight * Gallery.PIXEL_DENSITY);
221 int left = (int) (PADDING_LEFT * Gallery.PIXEL_DENSITY);
222 int top = (int) (PADDING_TOP * Gallery.PIXEL_DENSITY);
223 int right = left + maxWidth;
224 for (int i = 0; i != numOptions; ++i) {
225 Option option = options[i];
226 IconTitleDrawable drawable = option.mDrawable;
227 option.mBottom = top + rowHeight;
228 drawable.setBounds(left, top, right, option.mBottom);
232 // Resize the popup menu.
233 setSize(right + PADDING_RIGHT * Gallery.PIXEL_DENSITY, top + PADDING_BOTTOM * Gallery.PIXEL_DENSITY);
237 private int hitTestOptions(int x, int y) {
238 Option[] options = mOptions;
239 int numOptions = options.length;
242 if (numOptions != 0 && x >= 0 && x < mWidth && y >= 0) {
243 for (int i = 0; i != numOptions; ++i) {
244 if (y < options[i].mBottom) {
252 public interface Listener {
253 void onSelectionChanged(PopupMenu menu, int selectedIndex);
255 void onSelectionClicked(PopupMenu menu, int selectedIndex);
258 public static final class Option {
259 private final String mTitle;
260 private final Drawable mIcon;
261 private final Runnable mAction;
262 private IconTitleDrawable mDrawable = null;
265 public Option(String title, Drawable icon, Runnable action) {
272 private final class PopupTexture extends CanvasTexture {
273 private final NinePatch mBackground;
274 private final NinePatch mHighlightSelected;
275 private final Bitmap mTriangleBottom;
276 private final Rect mBackgroundRect = new Rect();
277 private int mTriangleX = 0;
279 public PopupTexture(Context context) {
280 super(Bitmap.Config.ARGB_8888);
281 Resources resources = context.getResources();
282 Bitmap background = BitmapFactory.decodeResource(resources, R.drawable.popup);
283 mBackground = new NinePatch(background, background.getNinePatchChunk(), null);
284 Bitmap highlightSelected = BitmapFactory.decodeResource(resources, R.drawable.popup_option_selected);
285 mHighlightSelected = new NinePatch(highlightSelected, highlightSelected.getNinePatchChunk(), null);
286 mTriangleBottom = BitmapFactory.decodeResource(resources, R.drawable.popup_triangle_bottom);
290 protected void onSizeChanged() {
291 mBackgroundRect.set(0, 0, getWidth(), getHeight() - (int) (POPUP_TRIANGLE_EXTRA_HEIGHT * Gallery.PIXEL_DENSITY));
295 protected void renderCanvas(Canvas canvas, Bitmap backing, int width, int height) {
296 // Draw the background.
297 backing.eraseColor(0);
298 mBackground.draw(canvas, mBackgroundRect, SRC_PAINT);
300 // Stamp the popup triangle over the appropriate region ignoring alpha.
301 Bitmap triangle = mTriangleBottom;
302 canvas.drawBitmap(triangle, mTriangleX, height - triangle.getHeight() - 1, SRC_PAINT);
304 // Draw the selection / focus highlight.
305 Option[] options = mOptions;
306 int selectedItem = mSelectedItem;
307 if (selectedItem != -1) {
308 Option option = options[selectedItem];
309 mHighlightSelected.draw(canvas, option.mDrawable.getBounds());
312 // Draw icons and titles.
313 int numOptions = options.length;
314 for (int i = 0; i != numOptions; ++i) {
315 options[i].mDrawable.draw(canvas);