PieMenu.java revision 376b54116e38b3b94c4d64663d1bff38352b0e59
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;
23376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport android.graphics.Canvas;
24376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport android.graphics.Paint;
25376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport android.graphics.Path;
26376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport android.graphics.Point;
27376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport android.graphics.PointF;
28376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport android.graphics.Rect;
29376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport android.graphics.RectF;
30376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport android.util.AttributeSet;
31376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport android.view.MotionEvent;
32376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport android.view.View;
33376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport android.widget.FrameLayout;
34376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
35376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport java.util.ArrayList;
36376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport java.util.HashMap;
37376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport java.util.List;
38376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport java.util.Map;
39376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
40376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbpublic class PieMenu extends FrameLayout {
41376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
42376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private static final int RADIUS_GAP = 10;
43376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
44376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    public interface PieController {
45376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        /**
46376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb         * called before menu opens to customize menu
47376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb         * returns if pie state has been changed
48376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb         */
49376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        public boolean onOpen();
50376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
51376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private Point mCenter;
52376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private int mRadius;
53376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private int mRadiusInc;
54376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private int mSlop;
55376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
56376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private boolean mOpen;
57376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private Paint mPaint;
58376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private Paint mSelectedPaint;
59376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private PieController mController;
60376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
61376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private Map<View, List<View>> mMenu;
62376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private List<View> mStack;
63376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
64376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private boolean mDirty;
65376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
66376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    /**
67376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param context
68376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param attrs
69376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param defStyle
70376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     */
71376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    public PieMenu(Context context, AttributeSet attrs, int defStyle) {
72376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        super(context, attrs, defStyle);
73376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        init(context);
74376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
75376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
76376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    /**
77376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param context
78376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param attrs
79376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     */
80376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    public PieMenu(Context context, AttributeSet attrs) {
81376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        super(context, attrs);
82376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        init(context);
83376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
84376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
85376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    /**
86376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param context
87376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     */
88376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    public PieMenu(Context context) {
89376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        super(context);
90376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        init(context);
91376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
92376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
93376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private void init(Context ctx) {
94376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        this.setTag(new MenuTag(0));
95376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mStack = new ArrayList<View>();
96376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mStack.add(this);
97376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        Resources res = ctx.getResources();
98376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mRadius = (int) res.getDimension(R.dimen.qc_radius);
99376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mRadiusInc = (int) res.getDimension(R.dimen.qc_radius_inc);
100376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mSlop = (int) res.getDimension(R.dimen.qc_slop);
101376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mPaint = new Paint();
102376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mPaint.setAntiAlias(true);
103376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mPaint.setColor(res.getColor(R.color.qc_slice_normal));
104376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mSelectedPaint = new Paint();
105376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mSelectedPaint.setAntiAlias(true);
106376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mSelectedPaint.setColor(res.getColor(R.color.qc_slice_active));
107376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mOpen = false;
108376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mMenu = new HashMap<View, List<View>>();
109376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        setWillNotDraw(false);
110376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        setDrawingCacheEnabled(false);
111376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mCenter = new Point(0,0);
112376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mDirty = true;
113376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
114376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
115376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    public void setController(PieController ctl) {
116376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mController = ctl;
117376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
118376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
119376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    public void setRadius(int r) {
120376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mRadius = r;
121376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        requestLayout();
122376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
123376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
124376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    public void setRadiusIncrement(int ri) {
125376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mRadiusInc = ri;
126376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        requestLayout();
127376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
128376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
129376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    /**
130376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * add a menu item to another item as a submenu
131376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param item
132376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param parent
133376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     */
134376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    public void addItem(View item, View parent) {
135376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        List<View> subs = mMenu.get(parent);
136376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        if (subs == null) {
137376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            subs = new ArrayList<View>();
138376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            mMenu.put(parent, subs);
139376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
140376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        subs.add(item);
141376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        MenuTag tag = new MenuTag(((MenuTag) parent.getTag()).level + 1);
142376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        item.setTag(tag);
143376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
144376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
145376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    public void addItem(View view) {
146376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        // add the item to the pie itself
147376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        addItem(view, this);
148376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
149376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
150376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    public void removeItem(View view) {
151376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        List<View> subs = mMenu.get(view);
152376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mMenu.remove(view);
153376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        for (View p : mMenu.keySet()) {
154376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            List<View> sl = mMenu.get(p);
155376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            if (sl != null) {
156376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                sl.remove(view);
157376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
158376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
159376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
160376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
161376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    public void clearItems(View parent) {
162376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        List<View> subs = mMenu.remove(parent);
163376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        if (subs != null) {
164376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            for (View sub: subs) {
165376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                clearItems(sub);
166376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
167376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
168376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
169376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
170376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    public void clearItems() {
171376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mMenu.clear();
172376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
173376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
174376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
175376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    public void show(boolean show) {
176376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mOpen = show;
177376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        if (mOpen) {
178376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            if (mController != null) {
179376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                boolean changed = mController.onOpen();
180376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
181376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            mDirty = true;
182376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
183376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        if (!show) {
184376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            // hide sub items
185376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            mStack.clear();
186376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            mStack.add(this);
187376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
188376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        invalidate();
189376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
190376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
191376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private void setCenter(int x, int y) {
192376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        if (x < mSlop) {
193376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            mCenter.x = 0;
194376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        } else {
195376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            mCenter.x = getWidth();
196376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
197376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mCenter.y = y;
198376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
199376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
200376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private boolean onTheLeft() {
201376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        return mCenter.x < mSlop;
202376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
203376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
204376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    @Override
205376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    protected void onDraw(Canvas canvas) {
206376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        if (mOpen) {
207376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            int radius = mRadius;
208376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            // start in the center for 0 level menu
209376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            float anchor = (float) Math.PI / 2;
210376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            PointF angles = new PointF();
211376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            int state = canvas.save();
212376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            if (onTheLeft()) {
213376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                // left handed
214376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                canvas.scale(-1, 1);
215376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
216376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            for (View parent : mStack) {
217376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                List<View> subs = mMenu.get(parent);
218376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                if (subs != null) {
219376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                    setGeometry(anchor, subs.size(), angles);
220376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                }
221376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                anchor = drawSlices(canvas, subs, radius, angles.x, angles.y);
222376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                radius += mRadiusInc + RADIUS_GAP;
223376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
224376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            canvas.restoreToCount(state);
225376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            mDirty = false;
226376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
227376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
228376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
229376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    /**
230376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * draw the set of slices
231376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param canvas
232376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param items
233376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param radius
234376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param start
235376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param sweep
236376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @return the angle of the selected slice
237376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     */
238376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private float drawSlices(Canvas canvas, List<View> items, int radius,
239376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            float start, float sweep) {
240376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        float angle = start + sweep / 2;
241376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        // gap between slices in degrees
242376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        float gap = 1f;
243376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        float newanchor = 0f;
244376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        for (View item : items) {
245376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            if (mDirty) {
246376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                item.measure(item.getLayoutParams().width,
247376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                        item.getLayoutParams().height);
248376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                int w = item.getMeasuredWidth();
249376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                int h = item.getMeasuredHeight();
250376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                int x = (int) (radius * Math.sin(angle));
251376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                int y =  mCenter.y - (int) (radius * Math.cos(angle)) - h / 2;
252376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                if (onTheLeft()) {
253376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                    x = mCenter.x + x - w / 2;
254376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                } else {
255376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                    x = mCenter.x - x - w / 2;
256376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                }
257376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                item.layout(x, y, x + w, y + h);
258376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
259376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            float itemstart = angle - sweep / 2;
260376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            int inner = radius - mRadiusInc / 2;
261376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            int outer = radius + mRadiusInc / 2;
262376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            Path slice = makeSlice(getDegrees(itemstart) - gap,
263376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                    getDegrees(itemstart + sweep) + gap,
264376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                    outer, inner, mCenter);
265376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            MenuTag tag = (MenuTag) item.getTag();
266376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            tag.start = itemstart;
267376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            tag.sweep = sweep;
268376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            tag.inner = inner;
269376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            tag.outer = outer;
270376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
271376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            Paint p = item.isPressed() ? mSelectedPaint : mPaint;
272376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            canvas.drawPath(slice, p);
273376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            int state = canvas.save();
274376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            if (onTheLeft()) {
275376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                canvas.scale(-1, 1);
276376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
277376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            canvas.translate(item.getX(), item.getY());
278376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            item.draw(canvas);
279376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            canvas.restoreToCount(state);
280376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            if (mStack.contains(item)) {
281376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                // item is anchor for sub menu
282376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                newanchor = angle;
283376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
284376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            angle += sweep;
285376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
286376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        return newanchor;
287376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
288376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
289376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    /**
290376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * converts a
291376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param angle from 0..PI to Android degrees (clockwise starting at 3 o'clock)
292376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @return skia angle
293376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     */
294376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private float getDegrees(double angle) {
295376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        return (float) (270 - 180 * angle / Math.PI);
296376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
297376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
298376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private Path makeSlice(float startangle, float endangle, int outerradius,
299376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            int innerradius, Point center) {
300376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        RectF bb = new RectF(center.x - outerradius, center.y - outerradius,
301376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                center.x + outerradius, center.y + outerradius);
302376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        RectF bbi = new RectF(center.x - innerradius, center.y - innerradius,
303376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                center.x + innerradius, center.y + innerradius);
304376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        Path path = new Path();
305376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        path.arcTo(bb, startangle, endangle - startangle, true);
306376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        path.arcTo(bbi, endangle, startangle - endangle);
307376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        path.close();
308376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        return path;
309376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
310376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
311376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    /**
312376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * all angles are 0 .. MATH.PI where 0 points up, and rotate counterclockwise
313376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * set the startangle and slice sweep in result
314376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param anchorangle : angle at which the menu is anchored
315376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param nslices
316376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param result : x : start, y : sweep
317376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     */
318376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private void setGeometry(float anchorangle, int nslices, PointF result) {
319376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        float span = (float) Math.min(anchorangle, Math.PI - anchorangle);
320376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        float sweep = 2 * span / (nslices + 1);
321376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        result.x = anchorangle - span + sweep / 2;
322376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        result.y = sweep;
323376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
324376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
325376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    // touch handling for pie
326376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
327376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    View mCurrentView;
328376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    Rect mHitRect = new Rect();
329376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
330376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    @Override
331376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    public boolean onTouchEvent(MotionEvent evt) {
332376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        float x = evt.getX();
333376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        float y = evt.getY();
334376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        int action = evt.getActionMasked();
335376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        int edges = evt.getEdgeFlags();
336376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        if (MotionEvent.ACTION_DOWN == action) {
337376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            if ((x > getWidth() - mSlop) || (x < mSlop)) {
338376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                setCenter((int) x, (int) y);
339376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                show(true);
340376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                return true;
341376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
342376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        } else if (MotionEvent.ACTION_UP == action) {
343376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            if (mOpen) {
344376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                View v = mCurrentView;
345376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                deselect();
346376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                if (v != null) {
347376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                    v.performClick();
348376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                }
349376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                show(false);
350376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                return true;
351376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
352376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        } else if (MotionEvent.ACTION_CANCEL == action) {
353376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            if (mOpen) {
354376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                show(false);
355376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
356376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            deselect();
357376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            return false;
358376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        } else if (MotionEvent.ACTION_MOVE == action) {
359376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            View v = findView((int) x, (int) y);
360376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            if (mCurrentView != v) {
361376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                onEnter(v);
362376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                invalidate();
363376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
364376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
365376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        // always re-dispatch event
366376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        return false;
367376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
368376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
369376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    /**
370376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * enter a slice for a view
371376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * updates model only
372376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param view
373376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     */
374376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private void onEnter(View view) {
375376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        // deselect
376376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        if (mCurrentView != null) {
377376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            if (getLevel(mCurrentView) >= getLevel(view)) {
378376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                mCurrentView.setPressed(false);
379376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
380376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
381376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        if (view != null) {
382376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            // clear up stack
383376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            MenuTag tag = (MenuTag) view.getTag();
384376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            int i = mStack.size() - 1;
385376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            while (i > 0) {
386376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                View v = mStack.get(i);
387376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                if (((MenuTag) v.getTag()).level >= tag.level) {
388376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                    v.setPressed(false);
389376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                    mStack.remove(i);
390376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                } else {
391376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                    break;
392376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                }
393376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                i--;
394376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
395376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            List<View> items = mMenu.get(view);
396376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            if (items != null) {
397376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                mStack.add(view);
398376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                mDirty = true;
399376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
400376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            view.setPressed(true);
401376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
402376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mCurrentView = view;
403376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
404376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
405376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private void deselect() {
406376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        if (mCurrentView != null) {
407376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            mCurrentView.setPressed(false);
408376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
409376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mCurrentView = null;
410376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
411376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
412376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private int getLevel(View v) {
413376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        if (v == null) return -1;
414376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        return ((MenuTag) v.getTag()).level;
415376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
416376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
417376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private View findView(int x, int y) {
418376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        // get angle and radius from x/y
419376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        float angle = (float) Math.PI / 2;
420376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        x = mCenter.x - x;
421376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        if (mCenter.x < mSlop) {
422376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            x = -x;
423376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
424376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        y = mCenter.y - y;
425376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        float dist = (float) Math.sqrt(x * x + y * y);
426376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        if (y > 0) {
427376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            angle = (float) Math.asin(x / dist);
428376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        } else if (y < 0) {
429376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            angle = (float) (Math.PI - Math.asin(x / dist ));
430376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
431376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        // find the matching item:
432376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        for (View parent : mStack) {
433376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            List<View> subs = mMenu.get(parent);
434376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            if (subs != null) {
435376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                for (View item : subs) {
436376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                    MenuTag tag = (MenuTag) item.getTag();
437376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                    if ((tag.inner < dist)
438376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                            && (tag.outer > dist)
439376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                            && (tag.start < angle)
440376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                            && (tag.start + tag.sweep > angle)) {
441376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                        return item;
442376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                    }
443376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                }
444376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
445376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
446376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        return null;
447376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
448376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
449376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    class MenuTag {
450376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
451376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        int level;
452376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        float start;
453376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        float sweep;
454376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        int inner;
455376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        int outer;
456376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
457376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        public MenuTag(int l) {
458376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            level = l;
459376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
460376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
461376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
462376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
463376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb}
464