Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8e12f8df authored by Oren Blasberg's avatar Oren Blasberg
Browse files

Add Cascading submenus implementation.

This change adds a new Cascading implementation of MenuPopup. When
enabled, submenus will show up in a cascading side by side fashion
when opened next to popup menus.

Bug: 20127825
Change-Id: Ie3c797fb5dbada7521cd93dc4171950af2be2ff7
parent b23976ef
Loading
Loading
Loading
Loading
+26 −20
Original line number Diff line number Diff line
@@ -85,6 +85,8 @@ public class ActionMenuPresenter extends BaseMenuPresenter
    private OpenOverflowRunnable mPostedOpenRunnable;
    private ActionMenuPopupCallback mPopupCallback;

    private final boolean mShowCascadingMenus;

    final PopupPresenterCallback mPopupPresenterCallback = new PopupPresenterCallback();
    int mOpenSubMenuId;

@@ -126,6 +128,9 @@ public class ActionMenuPresenter extends BaseMenuPresenter
    public ActionMenuPresenter(Context context) {
        super(context, com.android.internal.R.layout.action_menu_layout,
                com.android.internal.R.layout.action_menu_item_layout);

        mShowCascadingMenus = context.getResources().getBoolean(
                com.android.internal.R.bool.config_enableCascadingSubmenus);
    }

    @Override
@@ -500,14 +505,29 @@ public class ActionMenuPresenter extends BaseMenuPresenter
        }
        View anchor = findViewForItem(topSubMenu.getItem());
        if (anchor == null) {
            if (mOverflowButton == null) return false;
            anchor = mOverflowButton;
            // This means the submenu was opened from an overflow menu item, indicating the
            // MenuPopupHelper will handle opening the submenu via its MenuPopup. Return false to
            // ensure that the MenuPopup acts as presenter for the submenu, and acts on its
            // responsibility to display the new submenu.
            return false;
        }

        mOpenSubMenuId = subMenu.getItem().getItemId();
        mActionButtonPopup = new ActionButtonSubmenu(mContext, subMenu);
        mActionButtonPopup.setAnchorView(anchor);

        boolean preserveIconSpacing = false;
        final int count = subMenu.size();
        for (int i = 0; i < count; i++) {
            MenuItem childItem = subMenu.getItem(i);
            if (childItem.isVisible() && childItem.getIcon() != null) {
                preserveIconSpacing = true;
                break;
            }
        }

        mActionButtonPopup = new ActionButtonSubmenu(mContext, subMenu, anchor);
        mActionButtonPopup.setForceShowIcon(preserveIconSpacing);
        mActionButtonPopup.show();

        super.onSubMenuSelected(subMenu);
        return true;
    }
@@ -926,12 +946,9 @@ public class ActionMenuPresenter extends BaseMenuPresenter
    }

    private class ActionButtonSubmenu extends MenuPopupHelper {
        private SubMenuBuilder mSubMenu;

        public ActionButtonSubmenu(Context context, SubMenuBuilder subMenu) {
            super(context, subMenu, null, false,
        public ActionButtonSubmenu(Context context, SubMenuBuilder subMenu, View anchorView) {
            super(context, subMenu, anchorView, false,
                    com.android.internal.R.attr.actionOverflowMenuStyle);
            mSubMenu = subMenu;

            MenuItemImpl item = (MenuItemImpl) subMenu.getItem();
            if (!item.isActionButton()) {
@@ -940,17 +957,6 @@ public class ActionMenuPresenter extends BaseMenuPresenter
            }

            setCallback(mPopupPresenterCallback);

            boolean preserveIconSpacing = false;
            final int count = subMenu.size();
            for (int i = 0; i < count; i++) {
                MenuItem childItem = subMenu.getItem(i);
                if (childItem.isVisible() && childItem.getIcon() != null) {
                    preserveIconSpacing = true;
                    break;
                }
            }
            setForceShowIcon(preserveIconSpacing);
        }

        @Override
+55 −2
Original line number Diff line number Diff line
@@ -25,10 +25,8 @@ import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.IntProperty;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.TextView;
import android.widget.ListView;
@@ -128,6 +126,61 @@ public class DropDownListView extends ListView {
        setCacheColorHint(0); // Transparent, since the background drawable could be anything.
    }

    @Override
    protected boolean shouldShowSelector() {
        View selectedView = getSelectedView();
        return selectedView != null && selectedView.isEnabled() || super.shouldShowSelector();
    }

    protected void clearSelection() {
        setSelectedPositionInt(-1);
        setNextSelectedPositionInt(-1);
    }

    @Override
    public boolean onHoverEvent(MotionEvent ev) {
        final int action = ev.getActionMasked();
        if (action == MotionEvent.ACTION_HOVER_ENTER
                || action == MotionEvent.ACTION_HOVER_MOVE) {
            final int position = pointToPosition((int) ev.getX(), (int) ev.getY());
            if (position != INVALID_POSITION && position != mSelectedPosition) {
                final View hoveredItem = getChildAt(position - getFirstVisiblePosition());
                if (hoveredItem.isEnabled()) {
                    // Force a focus so that the proper selector state gets used when we update.
                    requestFocus();

                    positionSelector(position, hoveredItem);
                    setSelectedPositionInt(position);
                    setNextSelectedPositionInt(position);
                }
                updateSelectorState();
            }
        } else {
            // Do not cancel the selected position if the selection is visible by other reasons.
            if (!super.shouldShowSelector()) {
                setSelectedPositionInt(INVALID_POSITION);
            }
        }
        return super.onHoverEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        final int x = (int) event.getX();
        final int y = (int) event.getY();
        final int position = pointToPosition(x, y);
        if (position == INVALID_POSITION) {
            return super.onTouchEvent(event);
        }

        if (position != mSelectedPosition) {
            setSelectedPositionInt(position);
            setNextSelectedPositionInt(position);
        }

        return super.onTouchEvent(event);
    }

    /**
     * Handles forwarded events.
     *
+2 −1
Original line number Diff line number Diff line
@@ -69,7 +69,6 @@ public class ListPopupWindow implements ShowableListMenu {
    private static final int EXPAND_LIST_TIMEOUT = 250;

    private Context mContext;
    private PopupWindow mPopup;
    private ListAdapter mAdapter;
    private DropDownListView mDropDownList;

@@ -112,6 +111,8 @@ public class ListPopupWindow implements ShowableListMenu {

    private int mLayoutDirection;

    PopupWindow mPopup;

    /**
     * The provided prompt view should appear above list content.
     * 
+54 −41
Original line number Diff line number Diff line
@@ -17,10 +17,17 @@
package android.widget;

import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.transition.Transition;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.KeyEvent;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
import android.view.ViewGroup;
import android.view.ViewParent;

import com.android.internal.view.menu.ListMenuItemView;
import com.android.internal.view.menu.MenuAdapter;

/**
 * A MenuPopupWindow represents the popup window for menu.
@@ -40,52 +47,58 @@ public class MenuPopupWindow extends ListPopupWindow {
        return new MenuDropDownListView(context, hijackFocus);
    }

    static class MenuDropDownListView extends DropDownListView {
        private boolean mHoveredOnDisabledItem = false;
        private AccessibilityManager mAccessibilityManager;

        MenuDropDownListView(Context context, boolean hijackFocus) {
            super(context, hijackFocus);
            mAccessibilityManager = (AccessibilityManager) getContext().getSystemService(
                    Context.ACCESSIBILITY_SERVICE);
    public void setEnterTransition(Transition enterTransition) {
        mPopup.setEnterTransition(enterTransition);
    }

        @Override
        protected boolean shouldShowSelector() {
            return (isHovered() && !mHoveredOnDisabledItem) || super.shouldShowSelector();
    /**
     * Set whether this window is touch modal or if outside touches will be sent to
     * other windows behind it.
     */
    public void setTouchModal(boolean touchModal) {
        mPopup.setTouchModal(touchModal);
    }

        @Override
        public boolean onHoverEvent(MotionEvent ev) {
            mHoveredOnDisabledItem = false;
    private static class MenuDropDownListView extends DropDownListView {
        final int mAdvanceKey;
        final int mRetreatKey;

            // Accessibility system should already handle hover events and selections, menu does
            // not have to handle it by itself.
            if (mAccessibilityManager.isTouchExplorationEnabled()) {
                return super.onHoverEvent(ev);
            }
        public MenuDropDownListView(Context context, boolean hijackFocus) {
            super(context, hijackFocus);

            final int action = ev.getActionMasked();
            if (action == MotionEvent.ACTION_HOVER_ENTER
                    || action == MotionEvent.ACTION_HOVER_MOVE) {
                final int position = pointToPosition((int) ev.getX(), (int) ev.getY());
                if (position != INVALID_POSITION && position != mSelectedPosition) {
                    final View hoveredItem = getChildAt(position - getFirstVisiblePosition());
                    if (hoveredItem.isEnabled()) {
                        positionSelector(position, hoveredItem);
                        setSelectedPositionInt(position);
            final Resources res = context.getResources();
            final Configuration config = res.getConfiguration();
            if (config.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
                mAdvanceKey = KeyEvent.KEYCODE_DPAD_LEFT;
                mRetreatKey = KeyEvent.KEYCODE_DPAD_RIGHT;
            } else {
                        mHoveredOnDisabledItem = true;
                mAdvanceKey = KeyEvent.KEYCODE_DPAD_RIGHT;
                mRetreatKey = KeyEvent.KEYCODE_DPAD_LEFT;
            }
                    updateSelectorState();
        }
            } else {
                // Do not cancel the selected position if the selection is visible by other reasons.
                if (!super.shouldShowSelector()) {
                    setSelectedPositionInt(INVALID_POSITION);

        @Override
        public boolean onKeyDown(int keyCode, KeyEvent event) {
            ListMenuItemView selectedItem = (ListMenuItemView) getSelectedView();
            if (selectedItem != null && keyCode == mAdvanceKey) {
                if (selectedItem.isEnabled() &&
                        ((ListMenuItemView) selectedItem).getItemData().hasSubMenu()) {
                    performItemClick(
                            selectedItem,
                            getSelectedItemPosition(),
                            getSelectedItemId());
                }
                return true;
            } else if (selectedItem != null && keyCode == mRetreatKey) {
                setSelectedPositionInt(-1);
                setNextSelectedPositionInt(-1);

                ((MenuAdapter) getAdapter()).getAdapterMenu().close();
                return true;
            }
            return super.onHoverEvent(ev);
            return super.onKeyDown(keyCode, event);
        }

    }

}
 No newline at end of file
+9 −2
Original line number Diff line number Diff line
@@ -43,6 +43,7 @@ public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback {
    private final MenuBuilder mMenu;
    private final View mAnchor;
    private final MenuPopupHelper mPopup;
    private final boolean mShowCascadingMenus;

    private OnMenuItemClickListener mMenuItemClickListener;
    private OnDismissListener mDismissListener;
@@ -107,6 +108,8 @@ public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback {
    public PopupMenu(Context context, View anchor, int gravity, int popupStyleAttr,
            int popupStyleRes) {
        mContext = context;
        mShowCascadingMenus = context.getResources().getBoolean(
                com.android.internal.R.bool.config_enableCascadingSubmenus);
        mMenu = new MenuBuilder(context);
        mMenu.setCallback(this);
        mAnchor = anchor;
@@ -273,8 +276,12 @@ public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback {
            return true;
        }

        // Current menu will be dismissed by the normal helper, submenu will be shown in its place.
        if (!mShowCascadingMenus) {
            // Current menu will be dismissed by the normal helper, submenu will be shown in its
            // place. (If cascading menus are enabled, the cascading implementation will show the
            // submenu itself).
            new MenuPopupHelper(mContext, subMenu, mAnchor).show();
        }
        return true;
    }

Loading