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 java.util.HashMap;
     20 
     21 import javax.microedition.khronos.opengles.GL11;
     22 
     23 import android.content.Context;
     24 import android.view.MotionEvent;
     25 
     26 import com.cooliris.app.App;
     27 import com.cooliris.app.Res;
     28 
     29 public final class MenuBar extends Layer implements PopupMenu.Listener {
     30     public static final int HEIGHT = 45;
     31 
     32     public static final StringTexture.Config MENU_TITLE_STYLE_TEXT = new StringTexture.Config();
     33     private static final StringTexture.Config MENU_TITLE_STYLE = new StringTexture.Config();
     34     private static final int MENU_HIGHLIGHT_EDGE_WIDTH = 21;
     35     private static final int MENU_HIGHLIGHT_EDGE_INSET = 9;
     36     private static final long LONG_PRESS_THRESHOLD_MS = 350;
     37     private static final int HIT_TEST_MARGIN = 15;
     38 
     39     static {
     40         MENU_TITLE_STYLE.fontSize = 17 * App.PIXEL_DENSITY;
     41         MENU_TITLE_STYLE.sizeMode = StringTexture.Config.SIZE_EXACT;
     42         MENU_TITLE_STYLE.overflowMode = StringTexture.Config.OVERFLOW_FADE;
     43 
     44         MENU_TITLE_STYLE_TEXT.fontSize = 15 * App.PIXEL_DENSITY;
     45         MENU_TITLE_STYLE_TEXT.xalignment = StringTexture.Config.ALIGN_HCENTER;
     46         MENU_TITLE_STYLE_TEXT.sizeMode = StringTexture.Config.SIZE_EXACT;
     47         MENU_TITLE_STYLE_TEXT.overflowMode = StringTexture.Config.OVERFLOW_FADE;
     48     }
     49 
     50     private boolean mNeedsLayout = false;
     51     private Menu[] mMenus = {};
     52     private int mTouchMenu = -1;
     53     private int mTouchMenuItem = -1;
     54     private boolean mTouchActive = false;
     55     private boolean mTouchOverMenu = false;
     56     private final PopupMenu mSubmenu;
     57     private static final int BACKGROUND = Res.drawable.selection_menu_bg;
     58     private static final int SEPERATOR = Res.drawable.selection_menu_divider;
     59     private static final int MENU_HIGHLIGHT_LEFT = Res.drawable.selection_menu_bg_pressed_left;
     60     private static final int MENU_HIGHLIGHT_MIDDLE = Res.drawable.selection_menu_bg_pressed;
     61     private static final int MENU_HIGHLIGHT_RIGHT = Res.drawable.selection_menu_bg_pressed_right;
     62     private final HashMap<String, Texture> mTextureMap = new HashMap<String, Texture>();
     63     private GL11 mGL;
     64 
     65     private boolean mSecondTouch;
     66 
     67     public MenuBar(Context context) {
     68         mSubmenu = new PopupMenu(context);
     69         mSubmenu.setListener(this);
     70     }
     71 
     72     public Menu[] getMenus() {
     73         return mMenus;
     74     }
     75 
     76     public void setMenus(Menu[] menus) {
     77         mMenus = menus;
     78         mNeedsLayout = true;
     79     }
     80 
     81     public void updateMenu(Menu menu, int index) {
     82         mMenus[index] = menu;
     83         mNeedsLayout = true;
     84     }
     85 
     86     @Override
     87     protected void onHiddenChanged() {
     88         if (mHidden) {
     89             mSubmenu.close(false);
     90         }
     91     }
     92 
     93     @Override
     94     protected void onSizeChanged() {
     95         mNeedsLayout = true;
     96     }
     97 
     98     @Override
     99     public void generate(RenderView view, RenderView.Lists lists) {
    100         lists.blendedList.add(this);
    101         lists.hitTestList.add(this);
    102         lists.systemList.add(this);
    103         lists.updateList.add(this);
    104         mSubmenu.generate(view, lists);
    105     }
    106 
    107     @Override
    108     public void renderBlended(RenderView view, GL11 gl) {
    109         // Layout if needed.
    110         if (mNeedsLayout) {
    111             layoutMenus();
    112             mNeedsLayout = false;
    113         }
    114         if (mGL != gl) {
    115             mTextureMap.clear();
    116             mGL = gl;
    117         }
    118 
    119         // Draw the background.
    120         Texture background = view.getResource(BACKGROUND);
    121         int backgroundHeight = background.getHeight();
    122         int menuHeight = (int) (HEIGHT * App.PIXEL_DENSITY + 0.5f);
    123         int extra = background.getHeight() - menuHeight;
    124         view.draw2D(background, mX, mY - extra, mWidth, backgroundHeight);
    125 
    126         // Draw the separators.
    127         Menu[] menus = mMenus;
    128         int numMenus = menus.length;
    129         int y = (int) mY;
    130         if (view.bind(view.getResource(SEPERATOR))) {
    131             for (int i = 1; i < numMenus; ++i) {
    132                 view.draw2D(menus[i].x, y, 0, 1, menuHeight);
    133             }
    134         }
    135 
    136         // Draw the selection / focus highlight.
    137         int touchMenu = mTouchMenu;
    138         if (canDrawHighlight()) {
    139             drawHighlight(view, gl, touchMenu);
    140         }
    141 
    142         // Draw labels.
    143         float height = mHeight;
    144         for (int i = 0; i != numMenus; ++i) {
    145             // Draw the icon and title.
    146             Menu menu = menus[i];
    147             ResourceTexture icon = view.getResource(menu.icon);
    148 
    149             StringTexture titleTexture = (StringTexture) mTextureMap.get(menu.title);
    150             if (titleTexture == null) {
    151                 titleTexture = new StringTexture(menu.title, menu.config, menu.titleWidth, MENU_TITLE_STYLE.height);
    152                 view.loadTexture(titleTexture);
    153                 menu.titleTexture = titleTexture;
    154                 mTextureMap.put(menu.title, titleTexture);
    155             }
    156             int iconWidth = icon != null ? icon.getWidth() : 0;
    157             int width = iconWidth + menu.titleWidth;
    158             int offset = (menu.mWidth - width) / 2;
    159             if (icon != null) {
    160                 float iconY = y + (height - icon.getHeight()) / 2;
    161                 view.draw2D(icon, menu.x + offset, iconY);
    162             }
    163             float titleY = y + (height - MENU_TITLE_STYLE.height) / 2 + 1;
    164             view.draw2D(titleTexture, menu.x + offset + iconWidth, titleY);
    165         }
    166     }
    167 
    168     private void drawHighlight(RenderView view, GL11 gl, int touchMenu) {
    169         Texture highlightLeft = view.getResource(MENU_HIGHLIGHT_LEFT);
    170         Texture highlightMiddle = view.getResource(MENU_HIGHLIGHT_MIDDLE);
    171         Texture highlightRight = view.getResource(MENU_HIGHLIGHT_RIGHT);
    172 
    173         int height = highlightLeft.getHeight();
    174         int extra = height - (int) (HEIGHT * App.PIXEL_DENSITY);
    175         Menu menu = mMenus[touchMenu];
    176         int x = menu.x + (int) (MENU_HIGHLIGHT_EDGE_INSET * App.PIXEL_DENSITY);
    177         int width = menu.mWidth - (int) ((MENU_HIGHLIGHT_EDGE_INSET * 2) * App.PIXEL_DENSITY);
    178         int y = (int) mY - extra;
    179 
    180         // Draw left edge.
    181         view.draw2D(highlightLeft, x - MENU_HIGHLIGHT_EDGE_WIDTH * App.PIXEL_DENSITY, y, MENU_HIGHLIGHT_EDGE_WIDTH
    182                 * App.PIXEL_DENSITY, height);
    183 
    184         // Draw middle.
    185         view.draw2D(highlightMiddle, x, y, width, height);
    186 
    187         // Draw right edge.
    188         view.draw2D(highlightRight, x + width, y, MENU_HIGHLIGHT_EDGE_WIDTH * App.PIXEL_DENSITY, height);
    189     }
    190 
    191     private int hitTestMenu(int x, int y) {
    192         if (y > mY - HIT_TEST_MARGIN * App.PIXEL_DENSITY) {
    193             Menu[] menus = mMenus;
    194             for (int i = menus.length - 1; i >= 0; --i) {
    195                 if (x > menus[i].x) {
    196                     if (menus[i].onSelect != null || menus[i].options != null || menus[i].onSingleTapUp != null) {
    197                         return i;
    198                     } else {
    199                         return -1;
    200                     }
    201                 }
    202             }
    203         }
    204         return -1;
    205     }
    206 
    207     private void selectMenu(int index) {
    208         int oldIndex = mTouchMenu;
    209         if (oldIndex != index) {
    210             // Notify on deselect.
    211             Menu[] menus = mMenus;
    212             if (oldIndex != -1) {
    213                 Menu oldMenu = menus[oldIndex];
    214                 if (oldMenu.onDeselect != null) {
    215                     oldMenu.onDeselect.run();
    216                 }
    217             }
    218 
    219             // Select the new menu.
    220             mTouchMenu = index;
    221             mTouchMenuItem = -1;
    222 
    223             // Show the submenu for the selected menu if one is provided.
    224             PopupMenu submenu = mSubmenu;
    225             boolean didShow = false;
    226             if (index != -1) {
    227                 // Notify on select.
    228                 Menu menu = mMenus[index];
    229                 if (menu.onSelect != null) {
    230                     menu.onSelect.run();
    231                 }
    232 
    233                 // Show the popup menu if options are provided.
    234                 PopupMenu.Option[] options = menu.options;
    235                 if (options != null) {
    236                     int x = (int) mX + menu.x + menu.mWidth / 2;
    237                     int y = (int) mY;
    238                     didShow = true;
    239                     submenu.setOptions(options);
    240                     submenu.showAtPoint(x, y, (int) mWidth, (int) mHeight);
    241                 }
    242             }
    243             if (!didShow) {
    244                 submenu.close(true);
    245             }
    246         }
    247     }
    248 
    249     public void close() {
    250         int oldIndex = mTouchMenu;
    251         if (oldIndex != -1) {
    252             // Notify on deselect.
    253             Menu[] menus = mMenus;
    254             if (oldIndex != -1) {
    255                 Menu oldMenu = menus[oldIndex];
    256                 if (oldMenu.onDeselect != null) {
    257                     oldMenu.onDeselect.run();
    258                 }
    259             }
    260             oldIndex = -1;
    261         }
    262         selectMenu(-1);
    263         if (mSubmenu != null)
    264             mSubmenu.close(false);
    265     }
    266 
    267     @Override
    268     public boolean onTouchEvent(MotionEvent event) {
    269         int x = (int) event.getX();
    270         int y = (int) event.getY();
    271         int hit = hitTestMenu(x, y);
    272         int action = event.getAction();
    273         switch (action) {
    274         case MotionEvent.ACTION_DOWN:
    275             mTouchActive = true;
    276             if (mTouchMenu == hit) {
    277                 mSecondTouch = true;
    278             } else {
    279                 mSecondTouch = false;
    280             }
    281         case MotionEvent.ACTION_MOVE:
    282             // Determine which menu the touch is over.
    283             if (hit != -1) {
    284                 // Select the menu and invoke the action.
    285                 selectMenu(hit);
    286                 mTouchOverMenu = true;
    287             } else {
    288                 // Forward events outside the menubar to the active popup menu.
    289                 mTouchOverMenu = false;
    290             }
    291             mSubmenu.onTouchEvent(event);
    292             break;
    293         case MotionEvent.ACTION_UP:
    294             if (mTouchMenu == hit && mSecondTouch) {
    295                 mSubmenu.close(true);
    296                 mTouchMenu = -1;
    297                 break;
    298             }
    299             // Forward event to submenu.
    300             mSubmenu.onTouchEvent(event);
    301 
    302             // Leave the submenu open if the touch ends on the menu button in
    303             // less than
    304             // a time threshold.
    305             long elapsed = event.getEventTime() - event.getDownTime();
    306             if (hit != -1) {
    307                 // Notify on single tap.
    308                 Menu menu = mMenus[hit];
    309                 if (menu.onSingleTapUp != null) {
    310                     menu.onSingleTapUp.run();
    311                 }
    312                 if (menu.options == null)
    313                     selectMenu(-1);
    314             } else if (elapsed > LONG_PRESS_THRESHOLD_MS) {
    315                 selectMenu(-1);
    316             }
    317             break;
    318 
    319         case MotionEvent.ACTION_CANCEL:
    320             // Always deselect if canceled.
    321             selectMenu(-1);
    322             break;
    323         }
    324         return true;
    325     }
    326 
    327     private boolean canDrawHighlight() {
    328         return mTouchMenu != -1 && mTouchMenuItem == -1 && (!mTouchActive || mTouchOverMenu);
    329     }
    330 
    331     private void layoutMenus() {
    332         mTextureMap.clear();
    333 
    334         Menu[] menus = mMenus;
    335         int numMenus = menus.length;
    336         // we do the best attempt to fit the menu items and resize them
    337         // also, it tries to minimize different sized menu items
    338         // it finds the maximum width for a set of menu items, and checks
    339         // whether that width
    340         // can be used for all the cells, else, it goes to the next maximum
    341         // width, so on and
    342         // so forth
    343         if (numMenus != 0) {
    344             float viewWidth = mWidth;
    345             int occupiedWidth = 0;
    346             int previousMaxWidth = Integer.MAX_VALUE;
    347             int totalDesiredWidth = 0;
    348 
    349             for (int i = 0; i < numMenus; i++) {
    350                 totalDesiredWidth += menus[i].computeRequiredWidth();
    351             }
    352 
    353             if (totalDesiredWidth > viewWidth) {
    354                 // Just split the menus up by available size / nr of menus.
    355                 int widthPerMenu = (int) Math.floor(viewWidth / numMenus);
    356                 int x = 0;
    357 
    358                 for (int i = 0; i < numMenus; i++) {
    359                     Menu menu = menus[i];
    360                     menu.x = x;
    361                     menu.mWidth = widthPerMenu;
    362                     menu.titleWidth = widthPerMenu - (20 + (menu.icon != 0 ? 45 : 0)); // TODO
    363                                                                                        // factor
    364                                                                                        // out
    365                                                                                        // padding
    366                                                                                        // etc
    367 
    368                     // fix up rounding errors by adding the last pixel to the
    369                     // last menu.
    370                     if (i == numMenus - 1) {
    371                         menu.mWidth = (int) viewWidth - x;
    372                     }
    373                     x += widthPerMenu;
    374 
    375                 }
    376             } else {
    377                 boolean foundANewMaxWidth = true;
    378                 int menusProcessed = 0;
    379 
    380                 while (foundANewMaxWidth && menusProcessed < numMenus) {
    381                     foundANewMaxWidth = false;
    382                     int maxWidth = 0;
    383                     for (int i = 0; i < numMenus; ++i) {
    384                         int width = menus[i].computeRequiredWidth();
    385                         if (width > maxWidth && width < previousMaxWidth) {
    386                             foundANewMaxWidth = true;
    387                             maxWidth = width;
    388                         }
    389                     }
    390                     // can all the menus have this width
    391                     int cumulativeWidth = maxWidth * (numMenus - menusProcessed) + occupiedWidth;
    392                     if (cumulativeWidth < viewWidth || !foundANewMaxWidth || menusProcessed == numMenus - 1) {
    393                         float delta = (viewWidth - cumulativeWidth) / numMenus;
    394                         if (delta < 0) {
    395                             delta = 0;
    396                         }
    397                         int x = 0;
    398                         for (int i = 0; i < numMenus; ++i) {
    399                             Menu menu = menus[i];
    400                             menu.x = x;
    401                             float width = menus[i].computeRequiredWidth();
    402                             if (width < maxWidth) {
    403                                 width = maxWidth + delta;
    404                             } else {
    405                                 width += delta;
    406                             }
    407                             menu.mWidth = (int) width;
    408                             menu.titleWidth = StringTexture.computeTextWidthForConfig(menu.title, menu.config); // (int)menus[i].title.computeTextWidth();
    409                             x += width;
    410                         }
    411                         break;
    412                     } else {
    413                         ++menusProcessed;
    414                         previousMaxWidth = maxWidth;
    415                         occupiedWidth += maxWidth;
    416                     }
    417                 }
    418             }
    419         }
    420     }
    421 
    422     public static final class Menu {
    423         public final String title;
    424         public StringTexture titleTexture = null;
    425         public int titleWidth = 0;
    426         public final StringTexture.Config config;
    427         public final int icon;
    428         public final Runnable onSelect;
    429         public final Runnable onDeselect;
    430         public final Runnable onSingleTapUp;
    431         public final boolean resizeToAccomodate;
    432         public PopupMenu.Option[] options;
    433         private int x;
    434         private int mWidth;
    435         private static final float ICON_WIDTH = 45.0f;
    436 
    437         public static final class Builder {
    438             private final String title;
    439             private StringTexture.Config config;
    440             private int icon = 0;
    441             private Runnable onSelect = null;
    442             private Runnable onDeselect = null;
    443             private Runnable onSingleTapUp = null;
    444             private PopupMenu.Option[] options = null;
    445             private boolean resizeToAccomodate;
    446 
    447             public Builder(String title) {
    448                 this.title = title;
    449                 config = MENU_TITLE_STYLE;
    450             }
    451 
    452             public Builder config(StringTexture.Config config) {
    453                 this.config = config;
    454                 return this;
    455             }
    456 
    457             public Builder resizeToAccomodate() {
    458                 this.resizeToAccomodate = true;
    459                 return this;
    460             }
    461 
    462             public Builder icon(int icon) {
    463                 this.icon = icon;
    464                 return this;
    465             }
    466 
    467             public Builder onSelect(Runnable onSelect) {
    468                 this.onSelect = onSelect;
    469                 return this;
    470             }
    471 
    472             public Builder onDeselect(Runnable onDeselect) {
    473                 this.onDeselect = onDeselect;
    474                 return this;
    475             }
    476 
    477             public Builder onSingleTapUp(Runnable onSingleTapUp) {
    478                 this.onSingleTapUp = onSingleTapUp;
    479                 return this;
    480             }
    481 
    482             public Builder options(PopupMenu.Option[] options) {
    483                 this.options = options;
    484                 return this;
    485             }
    486 
    487             public Menu build() {
    488                 return new Menu(this);
    489             }
    490         }
    491 
    492         private Menu(Builder builder) {
    493             config = builder.config;
    494             title = builder.title; // new StringTexture(builder.title, config);
    495             icon = builder.icon;
    496             onSelect = builder.onSelect;
    497             onDeselect = builder.onDeselect;
    498             onSingleTapUp = builder.onSingleTapUp;
    499             options = builder.options;
    500             resizeToAccomodate = builder.resizeToAccomodate;
    501         }
    502 
    503         public int computeRequiredWidth() {
    504             int width = 0;
    505             if (icon != 0) {
    506                 width += (ICON_WIDTH); // * App.PIXEL_DENSITY);
    507             }
    508             if (title != null) {
    509                 width += StringTexture.computeTextWidthForConfig(title, config);// title.computeTextWidth();
    510             }
    511             // pad it
    512             width += 20;
    513             if (width < HEIGHT)
    514                 width = HEIGHT;
    515             return width;
    516         }
    517 
    518     }
    519 
    520     public void onSelectionChanged(PopupMenu menu, int selectedIndex) {
    521         mTouchMenuItem = selectedIndex;
    522     }
    523 
    524     public void onSelectionClicked(PopupMenu menu, int selectedIndex) {
    525         selectMenu(-1);
    526     }
    527 }
    528