Home | History | Annotate | Download | only in media
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.cooliris.media;
     18 
     19 import javax.microedition.khronos.opengles.GL11;
     20 
     21 import android.content.Context;
     22 import android.content.res.Resources;
     23 import android.graphics.Bitmap;
     24 import android.graphics.BitmapFactory;
     25 import android.graphics.Canvas;
     26 import android.graphics.NinePatch;
     27 import android.graphics.Paint;
     28 import android.graphics.PorterDuff;
     29 import android.graphics.PorterDuffXfermode;
     30 import android.graphics.Rect;
     31 import android.graphics.drawable.Drawable;
     32 import android.os.SystemClock;
     33 import android.text.TextPaint;
     34 import android.view.MotionEvent;
     35 
     36 import com.cooliris.app.App;
     37 import com.cooliris.app.Res;
     38 
     39 public final class PopupMenu extends Layer {
     40     private static final int POPUP_TRIANGLE_EXTRA_HEIGHT = 14;
     41     private static final int POPUP_TRIANGLE_X_MARGIN = 16;
     42     private static final int POPUP_Y_OFFSET = 20;
     43     private static final Paint SRC_PAINT = new Paint();
     44     private static final int PADDING_LEFT = 10 + 5;
     45     private static final int PADDING_TOP = 10 + 3;
     46     private static final int PADDING_RIGHT = 10 + 5;
     47     private static final int PADDING_BOTTOM = 30 + 10;
     48     private static final int ICON_TITLE_MIN_WIDTH = 100;
     49     private static final IconTitleDrawable.Config ICON_TITLE_CONFIG;
     50 
     51     private PopupTexture mPopupTexture;
     52     private Listener mListener = null;
     53     private Option[] mOptions = {};
     54     private boolean mNeedsLayout = false;
     55     private boolean mShow = false;
     56     private final FloatAnim mShowAnim = new FloatAnim(0f);
     57     private int mRowHeight = 36;
     58     private int mSelectedItem = -1;
     59 
     60     static {
     61         TextPaint paint = new TextPaint();
     62         paint.setTextSize(17f * App.PIXEL_DENSITY);
     63         paint.setColor(0xffffffff);
     64         paint.setAntiAlias(true);
     65         ICON_TITLE_CONFIG = new IconTitleDrawable.Config((int) (45 * App.PIXEL_DENSITY), (int) (34 * App.PIXEL_DENSITY),
     66                 paint);
     67         SRC_PAINT.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
     68     }
     69 
     70     public PopupMenu(Context context) {
     71         mPopupTexture = new PopupTexture(context);
     72         setHidden(true);
     73     }
     74 
     75     public void setListener(Listener listener) {
     76         mListener = listener;
     77     }
     78 
     79     public void setOptions(Option[] options) {
     80         close(false);
     81         mOptions = options;
     82         mNeedsLayout = true;
     83     }
     84 
     85     public void showAtPoint(int pointX, int pointY, int outerWidth, int outerHeight) {
     86         // Compute layout if needed.
     87         if (mNeedsLayout) {
     88             layout();
     89         }
     90         // Try to center the popup over the target point.
     91         int width = (int) mWidth;
     92         int height = (int) mHeight;
     93         int widthOver2 = width / 2;
     94         int x = pointX - widthOver2;
     95         int y = pointY + POPUP_Y_OFFSET - height;
     96         int clampedX = Shared.clamp(x, 0, outerWidth - width);
     97         int triangleWidthOver2 = mPopupTexture.mTriangleBottom.getWidth() / 2;
     98         mPopupTexture.mTriangleX = Shared.clamp(widthOver2 + (x - clampedX) - triangleWidthOver2, POPUP_TRIANGLE_X_MARGIN, width
     99                 - POPUP_TRIANGLE_X_MARGIN * 2);
    100         mPopupTexture.setNeedsDraw();
    101         setPosition(clampedX, y);
    102 
    103         // Fade in the menu if it is not already visible, otherwise snap to the
    104         // new location.
    105         // if (!mShow) {
    106         mShow = true;
    107         setHidden(false);
    108         mShowAnim.setValue(0);
    109         mShowAnim.animateValue(1f, 0.4f, SystemClock.uptimeMillis());
    110         // }
    111     }
    112 
    113     public void close(boolean fadeOut) {
    114         if (mShow) {
    115             if (fadeOut) {
    116                 mShowAnim.animateValue(0, 0.3f, SystemClock.uptimeMillis());
    117             } else {
    118                 mShowAnim.setValue(0);
    119             }
    120             mShow = false;
    121             mSelectedItem = -1;
    122         }
    123 
    124     }
    125 
    126     @Override
    127     public void generate(RenderView view, RenderView.Lists lists) {
    128         lists.blendedList.add(this);
    129         lists.hitTestList.add(this);
    130         lists.systemList.add(this);
    131         lists.updateList.add(this);
    132     }
    133 
    134     @Override
    135     protected void onSizeChanged() {
    136         super.onSizeChanged();
    137         mPopupTexture.setSize((int) mWidth, (int) mHeight);
    138     }
    139 
    140     @Override
    141     protected void onSurfaceCreated(RenderView view, GL11 gl) {
    142         close(false);
    143     }
    144 
    145     @Override
    146     public boolean onTouchEvent(MotionEvent event) {
    147         int hit = hitTestOptions((int) event.getX(), (int) event.getY());
    148         switch (event.getAction()) {
    149         case MotionEvent.ACTION_DOWN:
    150         case MotionEvent.ACTION_MOVE:
    151             setSelectedItem(hit);
    152             break;
    153         case MotionEvent.ACTION_UP:
    154             if (hit != -1 && mSelectedItem == hit) {
    155                 mOptions[hit].mAction.run();
    156                 if (mListener != null) {
    157                     mListener.onSelectionClicked(this, hit);
    158                 }
    159             }
    160         case MotionEvent.ACTION_CANCEL:
    161             setSelectedItem(-1);
    162             break;
    163         }
    164         return true;
    165     }
    166 
    167     private void setSelectedItem(int hit) {
    168         if (mSelectedItem != hit) {
    169             mSelectedItem = hit;
    170             mPopupTexture.setNeedsDraw();
    171             if (mListener != null) {
    172                 mListener.onSelectionChanged(this, hit);
    173             }
    174         }
    175     }
    176 
    177     @Override
    178     public boolean update(RenderView view, float timeElapsed) {
    179         return (mShowAnim.getTimeRemaining(SystemClock.uptimeMillis()) > 0);
    180     }
    181 
    182     @Override
    183     public void renderBlended(RenderView view, GL11 gl) {
    184         // Hide the layer if the close animation is complete.
    185         float showRatio = mShowAnim.getValue(SystemClock.uptimeMillis());
    186         boolean show = mShow;
    187         if (showRatio < 0.003f && !show) {
    188             setHidden(true);
    189         }
    190 
    191         // Draw the selection menu with the show animation.
    192         int x = (int) mX;
    193         int y = (int) mY;
    194         if (show && showRatio < 1f) {
    195             // Animate the scale as well for the open animation.
    196             float scale;
    197             float split = 0.7f;
    198             if (showRatio < split) {
    199                 scale = 0.8f + 0.3f * showRatio / split;
    200             } else {
    201                 scale = 1f + ((1f - showRatio) / (1f - split)) * 0.1f;
    202             }
    203             mPopupTexture.drawWithEffect(view, gl, x, y, 0.5f, 0.65f, showRatio, scale);
    204         } else {
    205             if (showRatio < 1f) {
    206                 view.setAlpha(showRatio);
    207             }
    208             mPopupTexture.draw(view, gl, x, y);
    209             if (showRatio < 1f) {
    210                 view.resetColor();
    211             }
    212         }
    213 
    214     }
    215 
    216     private void layout() {
    217         // Mark as not needing layout.
    218         mNeedsLayout = false;
    219 
    220         // Measure the menu options.
    221         Option[] options = mOptions;
    222         int numOptions = options.length;
    223         int maxWidth = (int) (ICON_TITLE_MIN_WIDTH * App.PIXEL_DENSITY);
    224         for (int i = 0; i != numOptions; ++i) {
    225             Option option = options[i];
    226             IconTitleDrawable drawable = option.mDrawable;
    227             if (drawable == null) {
    228                 drawable = new IconTitleDrawable(option.mTitle, option.mIcon, ICON_TITLE_CONFIG);
    229                 option.mDrawable = drawable;
    230             }
    231             int width = drawable.getIntrinsicWidth();
    232             if (width > maxWidth) {
    233                 maxWidth = width;
    234             }
    235         }
    236 
    237         // Layout the menu options.
    238         int rowHeight = (int) (mRowHeight * App.PIXEL_DENSITY);
    239         int left = (int) (PADDING_LEFT * App.PIXEL_DENSITY);
    240         int top = (int) (PADDING_TOP * App.PIXEL_DENSITY);
    241         int right = left + maxWidth;
    242         for (int i = 0; i != numOptions; ++i) {
    243             Option option = options[i];
    244             IconTitleDrawable drawable = option.mDrawable;
    245             option.mBottom = top + rowHeight;
    246             drawable.setBounds(left, top, right, option.mBottom);
    247             top += rowHeight;
    248         }
    249 
    250         // Resize the popup menu.
    251         setSize(right + PADDING_RIGHT * App.PIXEL_DENSITY, top + PADDING_BOTTOM * App.PIXEL_DENSITY);
    252 
    253     }
    254 
    255     private int hitTestOptions(int x, int y) {
    256         Option[] options = mOptions;
    257         int numOptions = options.length;
    258         x -= mX;
    259         y -= mY;
    260         if (numOptions != 0 && x >= 0 && x < mWidth && y >= 0) {
    261             for (int i = 0; i != numOptions; ++i) {
    262                 if (y < options[i].mBottom) {
    263                     return i;
    264                 }
    265             }
    266         }
    267         return -1;
    268     }
    269 
    270     public interface Listener {
    271         void onSelectionChanged(PopupMenu menu, int selectedIndex);
    272 
    273         void onSelectionClicked(PopupMenu menu, int selectedIndex);
    274     }
    275 
    276     public static final class Option {
    277         private final String mTitle;
    278         private final Drawable mIcon;
    279         private final Runnable mAction;
    280         private IconTitleDrawable mDrawable = null;
    281         private int mBottom;
    282 
    283         public Option(String title, Drawable icon, Runnable action) {
    284             mTitle = title;
    285             mIcon = icon;
    286             mAction = action;
    287         }
    288     }
    289 
    290     private final class PopupTexture extends CanvasTexture {
    291         private final NinePatch mBackground;
    292         private final NinePatch mHighlightSelected;
    293         private final Bitmap mTriangleBottom;
    294         private final Rect mBackgroundRect = new Rect();
    295         private int mTriangleX = 0;
    296 
    297         public PopupTexture(Context context) {
    298             super(Bitmap.Config.ARGB_8888);
    299             Resources resources = context.getResources();
    300             Bitmap background = BitmapFactory.decodeResource(resources, Res.drawable.popup);
    301             mBackground = new NinePatch(background, background.getNinePatchChunk(), null);
    302             Bitmap highlightSelected = BitmapFactory.decodeResource(resources, Res.drawable.popup_option_selected);
    303             mHighlightSelected = new NinePatch(highlightSelected, highlightSelected.getNinePatchChunk(), null);
    304             mTriangleBottom = BitmapFactory.decodeResource(resources, Res.drawable.popup_triangle_bottom);
    305         }
    306 
    307         @Override
    308         protected void onSizeChanged() {
    309             mBackgroundRect.set(0, 0, getWidth(), getHeight() - (int) (POPUP_TRIANGLE_EXTRA_HEIGHT * App.PIXEL_DENSITY));
    310         }
    311 
    312         @Override
    313         protected void renderCanvas(Canvas canvas, Bitmap backing, int width, int height) {
    314             // Draw the background.
    315             backing.eraseColor(0);
    316             mBackground.draw(canvas, mBackgroundRect, SRC_PAINT);
    317 
    318             // Stamp the popup triangle over the appropriate region ignoring
    319             // alpha.
    320             Bitmap triangle = mTriangleBottom;
    321             canvas.drawBitmap(triangle, mTriangleX, height - triangle.getHeight() - 1, SRC_PAINT);
    322 
    323             // Draw the selection / focus highlight.
    324             Option[] options = mOptions;
    325             int selectedItem = mSelectedItem;
    326             if (selectedItem != -1) {
    327                 Option option = options[selectedItem];
    328                 mHighlightSelected.draw(canvas, option.mDrawable.getBounds());
    329             }
    330 
    331             // Draw icons and titles.
    332             int numOptions = options.length;
    333             for (int i = 0; i != numOptions; ++i) {
    334                 options[i].mDrawable.draw(canvas);
    335             }
    336         }
    337 
    338     }
    339 }
    340