PieMenu.java revision 2a56ecaf153d788a1acebc54b670347a1a58d693
1376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb/*
2376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb * Copyright (C) 2010 The Android Open Source Project
3376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb *
4376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb * Licensed under the Apache License, Version 2.0 (the "License");
5376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb * you may not use this file except in compliance with the License.
6376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb * You may obtain a copy of the License at
7376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb *
8376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb *      http://www.apache.org/licenses/LICENSE-2.0
9376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb *
10376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb * Unless required by applicable law or agreed to in writing, software
11376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb * distributed under the License is distributed on an "AS IS" BASIS,
12376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb * See the License for the specific language governing permissions and
14376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb * limitations under the License.
15376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb */
16376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
17376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbpackage com.android.browser.view;
18376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
19376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport com.android.browser.R;
20376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
21376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport android.content.Context;
22376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport android.content.res.Resources;
232a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolbimport android.graphics.Bitmap;
242a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolbimport android.graphics.BitmapShader;
25376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport android.graphics.Canvas;
262a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolbimport android.graphics.Matrix;
27376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport android.graphics.Paint;
28376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport android.graphics.Path;
29376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport android.graphics.Point;
30376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport android.graphics.PointF;
31376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport android.graphics.Rect;
32376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport android.graphics.RectF;
332a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolbimport android.graphics.Shader;
342a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolbimport android.graphics.drawable.Drawable;
35376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport android.util.AttributeSet;
36376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport android.view.MotionEvent;
372a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolbimport android.view.SoundEffectConstants;
38376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport android.view.View;
394be9bc7f7f38723ae8c4ca1d3203de212cf214bdJohn Reckimport android.view.ViewGroup;
40376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport android.widget.FrameLayout;
41376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
42376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport java.util.ArrayList;
43376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport java.util.HashMap;
44376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport java.util.List;
45376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport java.util.Map;
46376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
47376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbpublic class PieMenu extends FrameLayout {
48376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
49376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private static final int RADIUS_GAP = 10;
50376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
51376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    public interface PieController {
52376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        /**
53376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb         * called before menu opens to customize menu
54376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb         * returns if pie state has been changed
55376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb         */
56376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        public boolean onOpen();
57376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
58376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private Point mCenter;
59376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private int mRadius;
60376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private int mRadiusInc;
61376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private int mSlop;
62376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
63376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private boolean mOpen;
64376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private Paint mPaint;
65376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private Paint mSelectedPaint;
66376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private PieController mController;
67376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
68376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private Map<View, List<View>> mMenu;
69376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private List<View> mStack;
70376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
71376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private boolean mDirty;
72376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
732a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb    private Drawable mActiveDrawable;
742a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb    private Drawable mInactiveDrawable;
752a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb    private final Paint mActiveShaderPaint = new Paint();
762a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb    private final Paint mInactiveShaderPaint = new Paint();
772a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb    private final Matrix mActiveMatrix = new Matrix();
782a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb    private final Matrix mInactiveMatrix = new Matrix();
792a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb
802a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb    private BitmapShader mActiveShader;
812a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb    private BitmapShader mInactiveShader;
822a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb
832a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb
84376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    /**
85376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param context
86376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param attrs
87376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param defStyle
88376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     */
89376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    public PieMenu(Context context, AttributeSet attrs, int defStyle) {
90376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        super(context, attrs, defStyle);
91376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        init(context);
92376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
93376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
94376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    /**
95376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param context
96376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param attrs
97376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     */
98376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    public PieMenu(Context context, AttributeSet attrs) {
99376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        super(context, attrs);
100376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        init(context);
101376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
102376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
103376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    /**
104376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param context
105376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     */
106376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    public PieMenu(Context context) {
107376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        super(context);
108376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        init(context);
109376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
110376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
111376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private void init(Context ctx) {
112376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        this.setTag(new MenuTag(0));
113376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mStack = new ArrayList<View>();
114376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mStack.add(this);
115376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        Resources res = ctx.getResources();
116376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mRadius = (int) res.getDimension(R.dimen.qc_radius);
117376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mRadiusInc = (int) res.getDimension(R.dimen.qc_radius_inc);
118376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mSlop = (int) res.getDimension(R.dimen.qc_slop);
119376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mPaint = new Paint();
120376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mPaint.setAntiAlias(true);
121376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mPaint.setColor(res.getColor(R.color.qc_slice_normal));
122376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mSelectedPaint = new Paint();
123376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mSelectedPaint.setAntiAlias(true);
124376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mSelectedPaint.setColor(res.getColor(R.color.qc_slice_active));
125376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mOpen = false;
126376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mMenu = new HashMap<View, List<View>>();
127376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        setWillNotDraw(false);
128376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        setDrawingCacheEnabled(false);
129376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mCenter = new Point(0,0);
130376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mDirty = true;
1312a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb        mActiveShaderPaint.setStyle(Paint.Style.FILL);
1322a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb        mActiveShaderPaint.setAntiAlias(true);
1332a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb
1342a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb        mInactiveShaderPaint.setStyle(Paint.Style.FILL);
1352a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb        mInactiveShaderPaint.setAntiAlias(true);
1362a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb        mActiveDrawable = res.getDrawable(R.drawable.qc_background_selected);
1372a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb        mInactiveDrawable = res.getDrawable(R.drawable.qc_background_normal);
1382a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb
1392a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb        Bitmap activeTexture = getDrawableAsBitmap(mActiveDrawable,
1402a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb                mActiveDrawable.getIntrinsicWidth(),
1412a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb                mActiveDrawable.getIntrinsicHeight());
1422a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb        Bitmap inactiveTexture = getDrawableAsBitmap(mInactiveDrawable,
1432a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb                mInactiveDrawable.getIntrinsicWidth(),
1442a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb                mInactiveDrawable.getIntrinsicHeight());
1452a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb
1462a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb        mActiveShader = new BitmapShader(activeTexture,
1472a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb                Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
1482a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb        mActiveShaderPaint.setShader(mActiveShader);
1492a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb
1502a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb        mInactiveShader = new BitmapShader(inactiveTexture,
1512a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb                Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
1522a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb        mInactiveShaderPaint.setShader(mInactiveShader);
1532a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb
1542a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb    }
1552a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb
1562a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb    private static Bitmap getDrawableAsBitmap(Drawable drawable, int width, int height) {
1572a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb        Bitmap b = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
1582a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb        Canvas c = new Canvas(b);
1592a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb        drawable.setBounds(0, 0, width, height);
1602a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb        drawable.draw(c);
1612a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb        return b;
162376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
163376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
164376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    public void setController(PieController ctl) {
165376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mController = ctl;
166376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
167376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
168376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    public void setRadius(int r) {
169376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mRadius = r;
170376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        requestLayout();
171376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
172376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
173376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    public void setRadiusIncrement(int ri) {
174376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mRadiusInc = ri;
175376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        requestLayout();
176376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
177376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
178376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    /**
179376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * add a menu item to another item as a submenu
180376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param item
181376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param parent
182376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     */
183376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    public void addItem(View item, View parent) {
184376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        List<View> subs = mMenu.get(parent);
185376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        if (subs == null) {
186376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            subs = new ArrayList<View>();
187376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            mMenu.put(parent, subs);
188376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
189376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        subs.add(item);
190376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        MenuTag tag = new MenuTag(((MenuTag) parent.getTag()).level + 1);
191376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        item.setTag(tag);
192376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
193376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
194376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    public void addItem(View view) {
195376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        // add the item to the pie itself
196376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        addItem(view, this);
197376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
198376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
199376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    public void removeItem(View view) {
200376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        List<View> subs = mMenu.get(view);
201376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mMenu.remove(view);
202376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        for (View p : mMenu.keySet()) {
203376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            List<View> sl = mMenu.get(p);
204376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            if (sl != null) {
205376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                sl.remove(view);
206376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
207376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
208376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
209376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
210376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    public void clearItems(View parent) {
211376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        List<View> subs = mMenu.remove(parent);
212376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        if (subs != null) {
213376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            for (View sub: subs) {
214376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                clearItems(sub);
215376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
216376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
217376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
218376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
219376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    public void clearItems() {
220376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mMenu.clear();
221376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
222376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
223376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
224376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    public void show(boolean show) {
225376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mOpen = show;
226376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        if (mOpen) {
227376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            if (mController != null) {
228376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                boolean changed = mController.onOpen();
229376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
230376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            mDirty = true;
231376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
232376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        if (!show) {
233376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            // hide sub items
234376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            mStack.clear();
235376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            mStack.add(this);
236376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
237376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        invalidate();
238376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
239376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
240376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private void setCenter(int x, int y) {
241376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        if (x < mSlop) {
242376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            mCenter.x = 0;
243376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        } else {
244376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            mCenter.x = getWidth();
245376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
246376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mCenter.y = y;
247376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
248376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
249376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private boolean onTheLeft() {
250376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        return mCenter.x < mSlop;
251376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
252376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
253376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    @Override
254376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    protected void onDraw(Canvas canvas) {
255376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        if (mOpen) {
256376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            int radius = mRadius;
257376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            // start in the center for 0 level menu
258376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            float anchor = (float) Math.PI / 2;
259376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            PointF angles = new PointF();
260376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            int state = canvas.save();
261376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            if (onTheLeft()) {
262376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                // left handed
263376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                canvas.scale(-1, 1);
264376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
265376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            for (View parent : mStack) {
266376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                List<View> subs = mMenu.get(parent);
267376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                if (subs != null) {
268376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                    setGeometry(anchor, subs.size(), angles);
269376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                }
270376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                anchor = drawSlices(canvas, subs, radius, angles.x, angles.y);
271376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                radius += mRadiusInc + RADIUS_GAP;
272376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
273376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            canvas.restoreToCount(state);
274376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            mDirty = false;
275376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
276376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
277376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
278376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    /**
279376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * draw the set of slices
280376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param canvas
281376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param items
282376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param radius
283376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param start
284376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param sweep
285376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @return the angle of the selected slice
286376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     */
287376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private float drawSlices(Canvas canvas, List<View> items, int radius,
288376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            float start, float sweep) {
289376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        float angle = start + sweep / 2;
290376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        // gap between slices in degrees
291376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        float gap = 1f;
292376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        float newanchor = 0f;
293376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        for (View item : items) {
294376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            if (mDirty) {
295376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                item.measure(item.getLayoutParams().width,
296376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                        item.getLayoutParams().height);
297376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                int w = item.getMeasuredWidth();
298376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                int h = item.getMeasuredHeight();
299376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                int x = (int) (radius * Math.sin(angle));
300376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                int y =  mCenter.y - (int) (radius * Math.cos(angle)) - h / 2;
301376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                if (onTheLeft()) {
302376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                    x = mCenter.x + x - w / 2;
303376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                } else {
304376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                    x = mCenter.x - x - w / 2;
305376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                }
306376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                item.layout(x, y, x + w, y + h);
307376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
308376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            float itemstart = angle - sweep / 2;
309376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            int inner = radius - mRadiusInc / 2;
310376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            int outer = radius + mRadiusInc / 2;
311376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            Path slice = makeSlice(getDegrees(itemstart) - gap,
312376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                    getDegrees(itemstart + sweep) + gap,
313376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                    outer, inner, mCenter);
314376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            MenuTag tag = (MenuTag) item.getTag();
315376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            tag.start = itemstart;
316376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            tag.sweep = sweep;
317376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            tag.inner = inner;
318376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            tag.outer = outer;
319376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            int state = canvas.save();
3202a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb            int[] topLeft = new int[2];
3212a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb            getLocationInWindow(topLeft);
3222a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb            topLeft[0] = mCenter.x - outer;
3232a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb            topLeft[1] = mCenter.y - outer;
3242a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb            Paint paint = item.isPressed() ? mActiveShaderPaint : mInactiveShaderPaint;
3252a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb            drawClipped(canvas, paint, slice, topLeft, item.isPressed());
3262a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb            canvas.restoreToCount(state);
3272a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb            state = canvas.save();
328376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            if (onTheLeft()) {
329376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                canvas.scale(-1, 1);
330376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
331376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            canvas.translate(item.getX(), item.getY());
332376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            item.draw(canvas);
333376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            canvas.restoreToCount(state);
334376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            if (mStack.contains(item)) {
335376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                // item is anchor for sub menu
336376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                newanchor = angle;
337376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
338376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            angle += sweep;
339376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
340376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        return newanchor;
341376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
342376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
3432a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb    private void drawClipped(Canvas canvas, Paint paint, Path clipPath, int[] pos,
3442a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb            boolean selected) {
3452a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb        // TODO: We should change the matrix/shader only when needed
3462a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb        final Matrix matrix = selected ? mActiveMatrix : mInactiveMatrix;
3472a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb        matrix.setTranslate(pos[0], pos[1]);
3482a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb        (selected ? mActiveShader : mInactiveShader).setLocalMatrix(matrix);
3492a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb        canvas.drawPath(clipPath, paint);
3502a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb    }
3512a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb
3522a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb
353376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    /**
354376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * converts a
355376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param angle from 0..PI to Android degrees (clockwise starting at 3 o'clock)
356376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @return skia angle
357376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     */
358376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private float getDegrees(double angle) {
359376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        return (float) (270 - 180 * angle / Math.PI);
360376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
361376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
362376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private Path makeSlice(float startangle, float endangle, int outerradius,
363376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            int innerradius, Point center) {
364376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        RectF bb = new RectF(center.x - outerradius, center.y - outerradius,
365376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                center.x + outerradius, center.y + outerradius);
366376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        RectF bbi = new RectF(center.x - innerradius, center.y - innerradius,
367376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                center.x + innerradius, center.y + innerradius);
368376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        Path path = new Path();
369376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        path.arcTo(bb, startangle, endangle - startangle, true);
370376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        path.arcTo(bbi, endangle, startangle - endangle);
371376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        path.close();
372376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        return path;
373376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
374376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
375376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    /**
376376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * all angles are 0 .. MATH.PI where 0 points up, and rotate counterclockwise
377376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * set the startangle and slice sweep in result
378376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param anchorangle : angle at which the menu is anchored
379376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param nslices
380376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param result : x : start, y : sweep
381376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     */
382376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private void setGeometry(float anchorangle, int nslices, PointF result) {
383376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        float span = (float) Math.min(anchorangle, Math.PI - anchorangle);
384376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        float sweep = 2 * span / (nslices + 1);
385376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        result.x = anchorangle - span + sweep / 2;
386376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        result.y = sweep;
387376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
388376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
389376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    // touch handling for pie
390376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
391376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    View mCurrentView;
392376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    Rect mHitRect = new Rect();
393376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
394376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    @Override
395376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    public boolean onTouchEvent(MotionEvent evt) {
396376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        float x = evt.getX();
397376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        float y = evt.getY();
398376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        int action = evt.getActionMasked();
399376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        int edges = evt.getEdgeFlags();
400376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        if (MotionEvent.ACTION_DOWN == action) {
401376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            if ((x > getWidth() - mSlop) || (x < mSlop)) {
402376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                setCenter((int) x, (int) y);
403376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                show(true);
404376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                return true;
405376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
406376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        } else if (MotionEvent.ACTION_UP == action) {
407376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            if (mOpen) {
408376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                View v = mCurrentView;
409376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                deselect();
410376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                if (v != null) {
411376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                    v.performClick();
412376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                }
413376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                show(false);
414376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                return true;
415376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
416376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        } else if (MotionEvent.ACTION_CANCEL == action) {
417376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            if (mOpen) {
418376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                show(false);
419376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
420376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            deselect();
421376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            return false;
422376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        } else if (MotionEvent.ACTION_MOVE == action) {
423bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb            PointF polar = getPolar(x, y);
424bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb            if (polar.y > mRadius + 2 * mRadiusInc) {
425bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb                show(false);
426bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb                deselect();
4274be9bc7f7f38723ae8c4ca1d3203de212cf214bdJohn Reck                evt.setAction(MotionEvent.ACTION_DOWN);
4284be9bc7f7f38723ae8c4ca1d3203de212cf214bdJohn Reck                if (getParent() != null) {
4294be9bc7f7f38723ae8c4ca1d3203de212cf214bdJohn Reck                    ((ViewGroup) getParent()).dispatchTouchEvent(evt);
4304be9bc7f7f38723ae8c4ca1d3203de212cf214bdJohn Reck                }
431bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb                return false;
432bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb            }
433bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb            View v = findView(polar);
434376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            if (mCurrentView != v) {
435376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                onEnter(v);
436376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                invalidate();
437376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
438376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
439376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        // always re-dispatch event
440376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        return false;
441376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
442376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
443376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    /**
444376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * enter a slice for a view
445376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * updates model only
446376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param view
447376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     */
448376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private void onEnter(View view) {
449376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        // deselect
450376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        if (mCurrentView != null) {
451376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            if (getLevel(mCurrentView) >= getLevel(view)) {
452376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                mCurrentView.setPressed(false);
453376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
454376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
455376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        if (view != null) {
456376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            // clear up stack
4572a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb            playSoundEffect(SoundEffectConstants.CLICK);
458376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            MenuTag tag = (MenuTag) view.getTag();
459376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            int i = mStack.size() - 1;
460376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            while (i > 0) {
461376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                View v = mStack.get(i);
462376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                if (((MenuTag) v.getTag()).level >= tag.level) {
463376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                    v.setPressed(false);
464376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                    mStack.remove(i);
465376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                } else {
466376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                    break;
467376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                }
468376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                i--;
469376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
470376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            List<View> items = mMenu.get(view);
471376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            if (items != null) {
472376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                mStack.add(view);
473376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                mDirty = true;
474376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
475376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            view.setPressed(true);
476376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
477376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mCurrentView = view;
478376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
479376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
480376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private void deselect() {
481376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        if (mCurrentView != null) {
482376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            mCurrentView.setPressed(false);
483376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
484376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mCurrentView = null;
485376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
486376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
487376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private int getLevel(View v) {
488376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        if (v == null) return -1;
489376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        return ((MenuTag) v.getTag()).level;
490376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
491376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
492bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb    private PointF getPolar(float x, float y) {
493bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb        PointF res = new PointF();
494376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        // get angle and radius from x/y
495bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb        res.x = (float) Math.PI / 2;
496376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        x = mCenter.x - x;
497376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        if (mCenter.x < mSlop) {
498376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            x = -x;
499376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
500376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        y = mCenter.y - y;
501bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb        res.y = (float) Math.sqrt(x * x + y * y);
502376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        if (y > 0) {
503bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb            res.x = (float) Math.asin(x / res.y);
504376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        } else if (y < 0) {
505bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb            res.x = (float) (Math.PI - Math.asin(x / res.y ));
506376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
507bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb        return res;
508bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb    }
509bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb
510bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb    /**
511bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb     *
512bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb     * @param polar x: angle, y: dist
513bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb     * @return
514bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb     */
515bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb    private View findView(PointF polar) {
516376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        // find the matching item:
517376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        for (View parent : mStack) {
518376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            List<View> subs = mMenu.get(parent);
519376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            if (subs != null) {
520376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                for (View item : subs) {
521376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                    MenuTag tag = (MenuTag) item.getTag();
522bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb                    if ((tag.inner < polar.y)
523bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb                            && (tag.outer > polar.y)
524bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb                            && (tag.start < polar.x)
525bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb                            && (tag.start + tag.sweep > polar.x)) {
526376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                        return item;
527376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                    }
528376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                }
529376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
530376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
531376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        return null;
532376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
533376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
534376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    class MenuTag {
535376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
536376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        int level;
537376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        float start;
538376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        float sweep;
539376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        int inner;
540376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        int outer;
541376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
542376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        public MenuTag(int l) {
543376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            level = l;
544376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
545376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
546376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
547376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
548376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb}
549