public static final class

MenuBar.Menu.Builder

extends Object
/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.cooliris.media;

import java.util.HashMap;

import javax.microedition.khronos.opengles.GL11;

import android.content.Context;
import android.view.MotionEvent;

import com.cooliris.app.App;
import com.cooliris.app.Res;

public final class MenuBar extends Layer implements PopupMenu.Listener {
    public static final int HEIGHT = 45;

    public static final StringTexture.Config MENU_TITLE_STYLE_TEXT = new StringTexture.Config();
    private static final StringTexture.Config MENU_TITLE_STYLE = new StringTexture.Config();
    private static final int MENU_HIGHLIGHT_EDGE_WIDTH = 21;
    private static final int MENU_HIGHLIGHT_EDGE_INSET = 9;
    private static final long LONG_PRESS_THRESHOLD_MS = 350;
    private static final int HIT_TEST_MARGIN = 15;

    static {
        MENU_TITLE_STYLE.fontSize = 17 * App.PIXEL_DENSITY;
        MENU_TITLE_STYLE.sizeMode = StringTexture.Config.SIZE_EXACT;
        MENU_TITLE_STYLE.overflowMode = StringTexture.Config.OVERFLOW_FADE;

        MENU_TITLE_STYLE_TEXT.fontSize = 15 * App.PIXEL_DENSITY;
        MENU_TITLE_STYLE_TEXT.xalignment = StringTexture.Config.ALIGN_HCENTER;
        MENU_TITLE_STYLE_TEXT.sizeMode = StringTexture.Config.SIZE_EXACT;
        MENU_TITLE_STYLE_TEXT.overflowMode = StringTexture.Config.OVERFLOW_FADE;
    }

    private boolean mNeedsLayout = false;
    private Menu[] mMenus = {};
    private int mTouchMenu = -1;
    private int mTouchMenuItem = -1;
    private boolean mTouchActive = false;
    private boolean mTouchOverMenu = false;
    private final PopupMenu mSubmenu;
    private static final int BACKGROUND = Res.drawable.selection_menu_bg;
    private static final int SEPERATOR = Res.drawable.selection_menu_divider;
    private static final int MENU_HIGHLIGHT_LEFT = Res.drawable.selection_menu_bg_pressed_left;
    private static final int MENU_HIGHLIGHT_MIDDLE = Res.drawable.selection_menu_bg_pressed;
    private static final int MENU_HIGHLIGHT_RIGHT = Res.drawable.selection_menu_bg_pressed_right;
    private final HashMap<String, Texture> mTextureMap = new HashMap<String, Texture>();
    private GL11 mGL;

    private boolean mSecondTouch;

    public MenuBar(Context context) {
        mSubmenu = new PopupMenu(context);
        mSubmenu.setListener(this);
    }

    public Menu[] getMenus() {
        return mMenus;
    }

    public void setMenus(Menu[] menus) {
        mMenus = menus;
        mNeedsLayout = true;
    }

    public void updateMenu(Menu menu, int index) {
        mMenus[index] = menu;
        mNeedsLayout = true;
    }

    @Override
    protected void onHiddenChanged() {
        if (mHidden) {
            mSubmenu.close(false);
        }
    }

    @Override
    protected void onSizeChanged() {
        mNeedsLayout = true;
    }

    @Override
    public void generate(RenderView view, RenderView.Lists lists) {
        lists.blendedList.add(this);
        lists.hitTestList.add(this);
        lists.systemList.add(this);
        lists.updateList.add(this);
        mSubmenu.generate(view, lists);
    }

    @Override
    public void renderBlended(RenderView view, GL11 gl) {
        // Layout if needed.
        if (mNeedsLayout) {
            layoutMenus();
            mNeedsLayout = false;
        }
        if (mGL != gl) {
            mTextureMap.clear();
            mGL = gl;
        }

        // Draw the background.
        Texture background = view.getResource(BACKGROUND);
        int backgroundHeight = background.getHeight();
        int menuHeight = (int) (HEIGHT * App.PIXEL_DENSITY + 0.5f);
        int extra = background.getHeight() - menuHeight;
        view.draw2D(background, mX, mY - extra, mWidth, backgroundHeight);

        // Draw the separators.
        Menu[] menus = mMenus;
        int numMenus = menus.length;
        int y = (int) mY;
        if (view.bind(view.getResource(SEPERATOR))) {
            for (int i = 1; i < numMenus; ++i) {
                view.draw2D(menus[i].x, y, 0, 1, menuHeight);
            }
        }

        // Draw the selection / focus highlight.
        int touchMenu = mTouchMenu;
        if (canDrawHighlight()) {
            drawHighlight(view, gl, touchMenu);
        }

        // Draw labels.
        float height = mHeight;
        for (int i = 0; i != numMenus; ++i) {
            // Draw the icon and title.
            Menu menu = menus[i];
            ResourceTexture icon = view.getResource(menu.icon);

            StringTexture titleTexture = (StringTexture) mTextureMap.get(menu.title);
            if (titleTexture == null) {
                titleTexture = new StringTexture(menu.title, menu.config, menu.titleWidth, MENU_TITLE_STYLE.height);
                view.loadTexture(titleTexture);
                menu.titleTexture = titleTexture;
                mTextureMap.put(menu.title, titleTexture);
            }
            int iconWidth = icon != null ? icon.getWidth() : 0;
            int width = iconWidth + menu.titleWidth;
            int offset = (menu.mWidth - width) / 2;
            if (icon != null) {
                float iconY = y + (height - icon.getHeight()) / 2;
                view.draw2D(icon, menu.x + offset, iconY);
            }
            float titleY = y + (height - MENU_TITLE_STYLE.height) / 2 + 1;
            view.draw2D(titleTexture, menu.x + offset + iconWidth, titleY);
        }
    }

    private void drawHighlight(RenderView view, GL11 gl, int touchMenu) {
        Texture highlightLeft = view.getResource(MENU_HIGHLIGHT_LEFT);
        Texture highlightMiddle = view.getResource(MENU_HIGHLIGHT_MIDDLE);
        Texture highlightRight = view.getResource(MENU_HIGHLIGHT_RIGHT);

        int height = highlightLeft.getHeight();
        int extra = height - (int) (HEIGHT * App.PIXEL_DENSITY);
        Menu menu = mMenus[touchMenu];
        int x = menu.x + (int) (MENU_HIGHLIGHT_EDGE_INSET * App.PIXEL_DENSITY);
        int width = menu.mWidth - (int) ((MENU_HIGHLIGHT_EDGE_INSET * 2) * App.PIXEL_DENSITY);
        int y = (int) mY - extra;

        // Draw left edge.
        view.draw2D(highlightLeft, x - MENU_HIGHLIGHT_EDGE_WIDTH * App.PIXEL_DENSITY, y, MENU_HIGHLIGHT_EDGE_WIDTH
                * App.PIXEL_DENSITY, height);

        // Draw middle.
        view.draw2D(highlightMiddle, x, y, width, height);

        // Draw right edge.
        view.draw2D(highlightRight, x + width, y, MENU_HIGHLIGHT_EDGE_WIDTH * App.PIXEL_DENSITY, height);
    }

    private int hitTestMenu(int x, int y) {
        if (y > mY - HIT_TEST_MARGIN * App.PIXEL_DENSITY) {
            Menu[] menus = mMenus;
            for (int i = menus.length - 1; i >= 0; --i) {
                if (x > menus[i].x) {
                    if (menus[i].onSelect != null || menus[i].options != null || menus[i].onSingleTapUp != null) {
                        return i;
                    } else {
                        return -1;
                    }
                }
            }
        }
        return -1;
    }

    private void selectMenu(int index) {
        int oldIndex = mTouchMenu;
        if (oldIndex != index) {
            // Notify on deselect.
            Menu[] menus = mMenus;
            if (oldIndex != -1) {
                Menu oldMenu = menus[oldIndex];
                if (oldMenu.onDeselect != null) {
                    oldMenu.onDeselect.run();
                }
            }

            // Select the new menu.
            mTouchMenu = index;
            mTouchMenuItem = -1;

            // Show the submenu for the selected menu if one is provided.
            PopupMenu submenu = mSubmenu;
            boolean didShow = false;
            if (index != -1) {
                // Notify on select.
                Menu menu = mMenus[index];
                if (menu.onSelect != null) {
                    menu.onSelect.run();
                }

                // Show the popup menu if options are provided.
                PopupMenu.Option[] options = menu.options;
                if (options != null) {
                    int x = (int) mX + menu.x + menu.mWidth / 2;
                    int y = (int) mY;
                    didShow = true;
                    submenu.setOptions(options);
                    submenu.showAtPoint(x, y, (int) mWidth, (int) mHeight);
                }
            }
            if (!didShow) {
                submenu.close(true);
            }
        }
    }

    public void close() {
        int oldIndex = mTouchMenu;
        if (oldIndex != -1) {
            // Notify on deselect.
            Menu[] menus = mMenus;
            if (oldIndex != -1) {
                Menu oldMenu = menus[oldIndex];
                if (oldMenu.onDeselect != null) {
                    oldMenu.onDeselect.run();
                }
            }
            oldIndex = -1;
        }
        selectMenu(-1);
        if (mSubmenu != null)
            mSubmenu.close(false);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        int hit = hitTestMenu(x, y);
        int action = event.getAction();
        switch (action) {
        case MotionEvent.ACTION_DOWN:
            mTouchActive = true;
            if (mTouchMenu == hit) {
                mSecondTouch = true;
            } else {
                mSecondTouch = false;
            }
        case MotionEvent.ACTION_MOVE:
            // Determine which menu the touch is over.
            if (hit != -1) {
                // Select the menu and invoke the action.
                selectMenu(hit);
                mTouchOverMenu = true;
            } else {
                // Forward events outside the menubar to the active popup menu.
                mTouchOverMenu = false;
            }
            mSubmenu.onTouchEvent(event);
            break;
        case MotionEvent.ACTION_UP:
            if (mTouchMenu == hit && mSecondTouch) {
                mSubmenu.close(true);
                mTouchMenu = -1;
                break;
            }
            // Forward event to submenu.
            mSubmenu.onTouchEvent(event);

            // Leave the submenu open if the touch ends on the menu button in
            // less than
            // a time threshold.
            long elapsed = event.getEventTime() - event.getDownTime();
            if (hit != -1) {
                // Notify on single tap.
                Menu menu = mMenus[hit];
                if (menu.onSingleTapUp != null) {
                    menu.onSingleTapUp.run();
                }
                if (menu.options == null)
                    selectMenu(-1);
            } else if (elapsed > LONG_PRESS_THRESHOLD_MS) {
                selectMenu(-1);
            }
            break;

        case MotionEvent.ACTION_CANCEL:
            // Always deselect if canceled.
            selectMenu(-1);
            break;
        }
        return true;
    }

    private boolean canDrawHighlight() {
        return mTouchMenu != -1 && mTouchMenuItem == -1 && (!mTouchActive || mTouchOverMenu);
    }

    private void layoutMenus() {
        mTextureMap.clear();

        Menu[] menus = mMenus;
        int numMenus = menus.length;
        // we do the best attempt to fit the menu items and resize them
        // also, it tries to minimize different sized menu items
        // it finds the maximum width for a set of menu items, and checks
        // whether that width
        // can be used for all the cells, else, it goes to the next maximum
        // width, so on and
        // so forth
        if (numMenus != 0) {
            float viewWidth = mWidth;
            int occupiedWidth = 0;
            int previousMaxWidth = Integer.MAX_VALUE;
            int totalDesiredWidth = 0;

            for (int i = 0; i < numMenus; i++) {
                totalDesiredWidth += menus[i].computeRequiredWidth();
            }

            if (totalDesiredWidth > viewWidth) {
                // Just split the menus up by available size / nr of menus.
                int widthPerMenu = (int) Math.floor(viewWidth / numMenus);
                int x = 0;

                for (int i = 0; i < numMenus; i++) {
                    Menu menu = menus[i];
                    menu.x = x;
                    menu.mWidth = widthPerMenu;
                    menu.titleWidth = widthPerMenu - (20 + (menu.icon != 0 ? 45 : 0)); // TODO
                                                                                       // factor
                                                                                       // out
                                                                                       // padding
                                                                                       // etc

                    // fix up rounding errors by adding the last pixel to the
                    // last menu.
                    if (i == numMenus - 1) {
                        menu.mWidth = (int) viewWidth - x;
                    }
                    x += widthPerMenu;

                }
            } else {
                boolean foundANewMaxWidth = true;
                int menusProcessed = 0;

                while (foundANewMaxWidth && menusProcessed < numMenus) {
                    foundANewMaxWidth = false;
                    int maxWidth = 0;
                    for (int i = 0; i < numMenus; ++i) {
                        int width = menus[i].computeRequiredWidth();
                        if (width > maxWidth && width < previousMaxWidth) {
                            foundANewMaxWidth = true;
                            maxWidth = width;
                        }
                    }
                    // can all the menus have this width
                    int cumulativeWidth = maxWidth * (numMenus - menusProcessed) + occupiedWidth;
                    if (cumulativeWidth < viewWidth || !foundANewMaxWidth || menusProcessed == numMenus - 1) {
                        float delta = (viewWidth - cumulativeWidth) / numMenus;
                        if (delta < 0) {
                            delta = 0;
                        }
                        int x = 0;
                        for (int i = 0; i < numMenus; ++i) {
                            Menu menu = menus[i];
                            menu.x = x;
                            float width = menus[i].computeRequiredWidth();
                            if (width < maxWidth) {
                                width = maxWidth + delta;
                            } else {
                                width += delta;
                            }
                            menu.mWidth = (int) width;
                            menu.titleWidth = StringTexture.computeTextWidthForConfig(menu.title, menu.config); // (int)menus[i].title.computeTextWidth();
                            x += width;
                        }
                        break;
                    } else {
                        ++menusProcessed;
                        previousMaxWidth = maxWidth;
                        occupiedWidth += maxWidth;
                    }
                }
            }
        }
    }

    public static final class Menu {
        public final String title;
        public StringTexture titleTexture = null;
        public int titleWidth = 0;
        public final StringTexture.Config config;
        public final int icon;
        public final Runnable onSelect;
        public final Runnable onDeselect;
        public final Runnable onSingleTapUp;
        public final boolean resizeToAccomodate;
        public PopupMenu.Option[] options;
        private int x;
        private int mWidth;
        private static final float ICON_WIDTH = 45.0f;

        public static final class Builder {
            private final String title;
            private StringTexture.Config config;
            private int icon = 0;
            private Runnable onSelect = null;
            private Runnable onDeselect = null;
            private Runnable onSingleTapUp = null;
            private PopupMenu.Option[] options = null;
            private boolean resizeToAccomodate;

            public Builder(String title) {
                this.title = title;
                config = MENU_TITLE_STYLE;
            }

            public Builder config(StringTexture.Config config) {
                this.config = config;
                return this;
            }

            public Builder resizeToAccomodate() {
                this.resizeToAccomodate = true;
                return this;
            }

            public Builder icon(int icon) {
                this.icon = icon;
                return this;
            }

            public Builder onSelect(Runnable onSelect) {
                this.onSelect = onSelect;
                return this;
            }

            public Builder onDeselect(Runnable onDeselect) {
                this.onDeselect = onDeselect;
                return this;
            }

            public Builder onSingleTapUp(Runnable onSingleTapUp) {
                this.onSingleTapUp = onSingleTapUp;
                return this;
            }

            public Builder options(PopupMenu.Option[] options) {
                this.options = options;
                return this;
            }

            public Menu build() {
                return new Menu(this);
            }
        }

        private Menu(Builder builder) {
            config = builder.config;
            title = builder.title; // new StringTexture(builder.title, config);
            icon = builder.icon;
            onSelect = builder.onSelect;
            onDeselect = builder.onDeselect;
            onSingleTapUp = builder.onSingleTapUp;
            options = builder.options;
            resizeToAccomodate = builder.resizeToAccomodate;
        }

        public int computeRequiredWidth() {
            int width = 0;
            if (icon != 0) {
                width += (ICON_WIDTH); // * App.PIXEL_DENSITY);
            }
            if (title != null) {
                width += StringTexture.computeTextWidthForConfig(title, config);// title.computeTextWidth();
            }
            // pad it
            width += 20;
            if (width < HEIGHT)
                width = HEIGHT;
            return width;
        }

    }

    public void onSelectionChanged(PopupMenu menu, int selectedIndex) {
        mTouchMenuItem = selectedIndex;
    }

    public void onSelectionClicked(PopupMenu menu, int selectedIndex) {
        selectMenu(-1);
    }
}