PieMenu.java revision a4befac241ec69791fd30e11355ed3dc10d7fb37
11cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato/*
21cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato * Copyright (C) 2010 The Android Open Source Project
31cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato *
41cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato * Licensed under the Apache License, Version 2.0 (the "License");
51cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato * you may not use this file except in compliance with the License.
61cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato * You may obtain a copy of the License at
71cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato *
81cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato *      http://www.apache.org/licenses/LICENSE-2.0
91cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato *
101cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato * Unless required by applicable law or agreed to in writing, software
111cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato * distributed under the License is distributed on an "AS IS" BASIS,
121cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
131cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato * See the License for the specific language governing permissions and
141cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato * limitations under the License.
151cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato */
161cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato
171cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onoratopackage com.android.browser.view;
181cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato
191cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onoratoimport com.android.browser.R;
201cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato
211cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onoratoimport android.content.Context;
221cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onoratoimport android.content.res.Resources;
23b13b9bdad2baf6ad1ec2e56b6b7598fa20f55fc4Mathias Agopianimport android.graphics.Canvas;
241cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onoratoimport android.graphics.Paint;
251cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onoratoimport android.graphics.Path;
261cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onoratoimport android.graphics.Point;
271cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onoratoimport android.graphics.PointF;
284528186e0d65fc68ef0dd1941aa2ac8aefcd55a3Christopher Tateimport android.graphics.RectF;
291cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onoratoimport android.graphics.drawable.Drawable;
301cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onoratoimport android.util.AttributeSet;
311cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onoratoimport android.view.MotionEvent;
321cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onoratoimport android.view.SoundEffectConstants;
331cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onoratoimport android.view.View;
341cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onoratoimport android.view.ViewGroup;
35a3804cf77f0edd93f6247a055cdafb856b117eecElliott Hughesimport android.widget.FrameLayout;
361cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato
371cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onoratoimport java.util.ArrayList;
381cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onoratoimport java.util.List;
391cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato
401cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onoratopublic class PieMenu extends FrameLayout {
411cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato
421cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato    private static final int MAX_LEVELS = 5;
431cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato
441cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato    public interface PieController {
451cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato        /**
461cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato         * called before menu opens to customize menu
471cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato         * returns if pie state has been changed
481cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato         */
491cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato        public boolean onOpen();
501cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato    }
511cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato
521cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato    /**
535f15d151b5101fadfe6cba1e8f4aa6367e8c603eJoe Onorato     * A view like object that lives off of the pie menu
541cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato     */
551cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato    public interface PieView {
562fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate
571cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato        public interface OnLayoutListener {
585f15d151b5101fadfe6cba1e8f4aa6367e8c603eJoe Onorato            public void onLayout(int ax, int ay, boolean left);
595f15d151b5101fadfe6cba1e8f4aa6367e8c603eJoe Onorato        }
602fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate
612fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate        public void setLayoutListener(OnLayoutListener l);
622fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate
632fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate        public void layout(int anchorX, int anchorY, boolean onleft, float angle,
642fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate                int parentHeight);
652fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate
662fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate        public void draw(Canvas c);
672fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate
682fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate        public boolean onTouchEvent(MotionEvent evt);
692fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate
702fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate    }
712fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate
722fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate    private Point mCenter;
732fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate    private int mRadius;
742fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate    private int mRadiusInc;
751cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato    private int mSlop;
762fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate    private int mTouchOffset;
772fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate
782fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate    private boolean mOpen;
792fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate    private PieController mController;
802fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate
812fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate    private List<PieItem> mItems;
822fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate    private int mLevels;
835baa3a62a97544669fba6d65a11c07f252e654ddSteve Block    private int[] mCounts;
842fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate    private PieView mPieView = null;
852fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate
862fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate    private Drawable mBackground;
871cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato    private Paint mNormalPaint;
881cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato    private Paint mSelectedPaint;
891cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato
901cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato    // touch handling
911cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato    PieItem mCurrentItem;
925f15d151b5101fadfe6cba1e8f4aa6367e8c603eJoe Onorato
931cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato    private boolean mUseBackground;
941cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato
951cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato    /**
961cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato     * @param context
975f15d151b5101fadfe6cba1e8f4aa6367e8c603eJoe Onorato     * @param attrs
981cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato     * @param defStyle
991cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato     */
1001cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato    public PieMenu(Context context, AttributeSet attrs, int defStyle) {
1011cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato        super(context, attrs, defStyle);
1021cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato        init(context);
1031cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato    }
1042fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate
1051cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato    /**
1061cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato     * @param context
1075f15d151b5101fadfe6cba1e8f4aa6367e8c603eJoe Onorato     * @param attrs
1081cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato     */
1091cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato    public PieMenu(Context context, AttributeSet attrs) {
1101cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato        super(context, attrs);
1111cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato        init(context);
1121cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato    }
1131cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato
1145f15d151b5101fadfe6cba1e8f4aa6367e8c603eJoe Onorato    /**
1155f15d151b5101fadfe6cba1e8f4aa6367e8c603eJoe Onorato     * @param context
1165f15d151b5101fadfe6cba1e8f4aa6367e8c603eJoe Onorato     */
1175f15d151b5101fadfe6cba1e8f4aa6367e8c603eJoe Onorato    public PieMenu(Context context) {
1185f15d151b5101fadfe6cba1e8f4aa6367e8c603eJoe Onorato        super(context);
1195f15d151b5101fadfe6cba1e8f4aa6367e8c603eJoe Onorato        init(context);
1205f15d151b5101fadfe6cba1e8f4aa6367e8c603eJoe Onorato    }
1215f15d151b5101fadfe6cba1e8f4aa6367e8c603eJoe Onorato
1225f15d151b5101fadfe6cba1e8f4aa6367e8c603eJoe Onorato    private void init(Context ctx) {
1235f15d151b5101fadfe6cba1e8f4aa6367e8c603eJoe Onorato        mItems = new ArrayList<PieItem>();
1245f15d151b5101fadfe6cba1e8f4aa6367e8c603eJoe Onorato        mLevels = 0;
1251cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato        mCounts = new int[MAX_LEVELS];
1261cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato        Resources res = ctx.getResources();
1271cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato        mRadius = (int) res.getDimension(R.dimen.qc_radius_start);
1284528186e0d65fc68ef0dd1941aa2ac8aefcd55a3Christopher Tate        mRadiusInc = (int) res.getDimension(R.dimen.qc_radius_increment);
1291cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato        mSlop = (int) res.getDimension(R.dimen.qc_slop);
1305f15d151b5101fadfe6cba1e8f4aa6367e8c603eJoe Onorato        mTouchOffset = (int) res.getDimension(R.dimen.qc_touch_offset);
1315f15d151b5101fadfe6cba1e8f4aa6367e8c603eJoe Onorato        mOpen = false;
1321cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato        setWillNotDraw(false);
1331cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato        setDrawingCacheEnabled(false);
1341cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato        mCenter = new Point(0,0);
1351cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato        mBackground = res.getDrawable(R.drawable.qc_background_normal);
1365baa3a62a97544669fba6d65a11c07f252e654ddSteve Block        mNormalPaint = new Paint();
1371cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato        mNormalPaint.setColor(res.getColor(R.color.qc_normal));
138a3804cf77f0edd93f6247a055cdafb856b117eecElliott Hughes        mNormalPaint.setAntiAlias(true);
1394528186e0d65fc68ef0dd1941aa2ac8aefcd55a3Christopher Tate        mSelectedPaint = new Paint();
1401cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato        mSelectedPaint.setColor(res.getColor(R.color.qc_selected));
1411cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato        mSelectedPaint.setAntiAlias(true);
1424528186e0d65fc68ef0dd1941aa2ac8aefcd55a3Christopher Tate    }
14303f4df4b3bf8b8828e795a0bf1f913e6e08f12f1Joe Onorato
1441cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato    public void setController(PieController ctl) {
1454528186e0d65fc68ef0dd1941aa2ac8aefcd55a3Christopher Tate        mController = ctl;
1461cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato    }
1474528186e0d65fc68ef0dd1941aa2ac8aefcd55a3Christopher Tate
1481cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato    public void setUseBackground(boolean useBackground) {
1491cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato        mUseBackground = useBackground;
1501cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato    }
1511cf587496fcb1d652bab9fc6792fb106b6fefaa4Joe Onorato
152    public void addItem(PieItem item) {
153        // add the item to the pie itself
154        mItems.add(item);
155        int l = item.getLevel();
156        mLevels = Math.max(mLevels, l);
157        mCounts[l]++;
158    }
159
160    public void removeItem(PieItem item) {
161        mItems.remove(item);
162    }
163
164    public void clearItems() {
165        mItems.clear();
166    }
167
168    private boolean onTheLeft() {
169        return mCenter.x < mSlop;
170    }
171
172    /**
173     * guaranteed has center set
174     * @param show
175     */
176    private void show(boolean show) {
177        mOpen = show;
178        if (mOpen) {
179            if (mController != null) {
180                boolean changed = mController.onOpen();
181            }
182            layoutPie();
183        }
184        if (!show) {
185            mCurrentItem = null;
186            mPieView = null;
187        }
188        invalidate();
189    }
190
191    private void setCenter(int x, int y) {
192        if (x < mSlop) {
193            mCenter.x = 0;
194        } else {
195            mCenter.x = getWidth();
196        }
197        mCenter.y = y;
198    }
199
200    private void layoutPie() {
201        float emptyangle = (float) Math.PI / 16;
202        int rgap = 2;
203        int inner = mRadius + rgap;
204        int outer = mRadius + mRadiusInc - rgap;
205        int radius = mRadius;
206        int gap = 1;
207        for (int i = 0; i < mLevels; i++) {
208            int level = i + 1;
209            float sweep = (float) (Math.PI - 2 * emptyangle) / mCounts[level];
210            float angle = emptyangle + sweep / 2;
211            for (PieItem item : mItems) {
212                if (item.getLevel() == level) {
213                    View view = item.getView();
214                    view.measure(view.getLayoutParams().width,
215                            view.getLayoutParams().height);
216                    int w = view.getMeasuredWidth();
217                    int h = view.getMeasuredHeight();
218                    int r = inner + (outer - inner) * 2 / 3;
219                    int x = (int) (r * Math.sin(angle));
220                    int y = mCenter.y - (int) (r * Math.cos(angle)) - h / 2;
221                    if (onTheLeft()) {
222                        x = mCenter.x + x - w / 2;
223                    } else {
224                        x = mCenter.x - x - w / 2;
225                    }
226                    view.layout(x, y, x + w, y + h);
227                    float itemstart = angle - sweep / 2;
228                    Path slice = makeSlice(getDegrees(itemstart) - gap,
229                            getDegrees(itemstart + sweep) + gap,
230                            outer, inner, mCenter);
231                    item.setGeometry(itemstart, sweep, inner, outer, slice);
232                    angle += sweep;
233                }
234            }
235            inner += mRadiusInc;
236            outer += mRadiusInc;
237        }
238    }
239
240
241    /**
242     * converts a
243     *
244     * @param angle from 0..PI to Android degrees (clockwise starting at 3
245     *        o'clock)
246     * @return skia angle
247     */
248    private float getDegrees(double angle) {
249        return (float) (270 - 180 * angle / Math.PI);
250    }
251
252    @Override
253    protected void onDraw(Canvas canvas) {
254        if (mOpen) {
255            int state;
256            if (mUseBackground) {
257                int w = mBackground.getIntrinsicWidth();
258                int h = mBackground.getIntrinsicHeight();
259                int left = mCenter.x - w;
260                int top = mCenter.y - h / 2;
261                mBackground.setBounds(left, top, left + w, top + h);
262                state = canvas.save();
263                if (onTheLeft()) {
264                    canvas.scale(-1, 1);
265                }
266                mBackground.draw(canvas);
267                canvas.restoreToCount(state);
268            }
269            for (PieItem item : mItems) {
270                Paint p = item.isSelected() ? mSelectedPaint : mNormalPaint;
271                state = canvas.save();
272                if (onTheLeft()) {
273                    canvas.scale(-1, 1);
274                }
275                drawPath(canvas, item.getPath(), p);
276                canvas.restoreToCount(state);
277                drawItem(canvas, item);
278            }
279            if (mPieView != null) {
280                mPieView.draw(canvas);
281            }
282        }
283    }
284
285    private void drawItem(Canvas canvas, PieItem item) {
286        int outer = item.getOuterRadius();
287        int left = mCenter.x - outer;
288        int top = mCenter.y - outer;
289        // draw the item view
290        View view = item.getView();
291        int state = canvas.save();
292        canvas.translate(view.getX(), view.getY());
293        view.draw(canvas);
294        canvas.restoreToCount(state);
295    }
296
297    private void drawPath(Canvas canvas, Path path, Paint paint) {
298        canvas.drawPath(path, paint);
299    }
300
301    private Path makeSlice(float start, float end, int outer, int inner, Point center) {
302        RectF bb =
303                new RectF(center.x - outer, center.y - outer, center.x + outer,
304                        center.y + outer);
305        RectF bbi =
306                new RectF(center.x - inner, center.y - inner, center.x + inner,
307                        center.y + inner);
308        Path path = new Path();
309        path.arcTo(bb, start, end - start, true);
310        path.arcTo(bbi, end, start - end);
311        path.close();
312        return path;
313    }
314
315    // touch handling for pie
316
317    @Override
318    public boolean onTouchEvent(MotionEvent evt) {
319        float x = evt.getX();
320        float y = evt.getY();
321        int action = evt.getActionMasked();
322        if (MotionEvent.ACTION_DOWN == action) {
323            if ((x > getWidth() - mSlop) || (x < mSlop)) {
324                setCenter((int) x, (int) y);
325                show(true);
326                return true;
327            }
328        } else if (MotionEvent.ACTION_UP == action) {
329            if (mOpen) {
330                boolean handled = false;
331                if (mPieView != null) {
332                    handled = mPieView.onTouchEvent(evt);
333                }
334                PieItem item = mCurrentItem;
335                deselect();
336                show(false);
337                if (!handled && (item != null)) {
338                    item.getView().performClick();
339                }
340                return true;
341            }
342        } else if (MotionEvent.ACTION_CANCEL == action) {
343            if (mOpen) {
344                show(false);
345            }
346            deselect();
347            return false;
348        } else if (MotionEvent.ACTION_MOVE == action) {
349            boolean handled = false;
350            PointF polar = getPolar(x, y);
351            int maxr = mRadius + mLevels * mRadiusInc + 50;
352            if (mPieView != null) {
353                handled = mPieView.onTouchEvent(evt);
354            }
355            if (handled) {
356                invalidate();
357                return false;
358            }
359            if (polar.y > maxr) {
360                deselect();
361                show(false);
362                evt.setAction(MotionEvent.ACTION_DOWN);
363                if (getParent() != null) {
364                    ((ViewGroup) getParent()).dispatchTouchEvent(evt);
365                }
366                return false;
367            }
368            PieItem item = findItem(polar);
369            if (mCurrentItem != item) {
370                onEnter(item);
371                if ((item != null) && item.isPieView()) {
372                    int cx = item.getView().getLeft() + (onTheLeft()
373                            ? item.getView().getWidth() : 0);
374                    int cy = item.getView().getTop();
375                    mPieView = item.getPieView();
376                    layoutPieView(mPieView, cx, cy,
377                            (item.getStartAngle() + item.getSweep()) / 2);
378                }
379                invalidate();
380            }
381        }
382        // always re-dispatch event
383        return false;
384    }
385
386    private void layoutPieView(PieView pv, int x, int y, float angle) {
387        pv.layout(x, y, onTheLeft(), angle, getHeight());
388    }
389
390    /**
391     * enter a slice for a view
392     * updates model only
393     * @param item
394     */
395    private void onEnter(PieItem item) {
396        // deselect
397        if (mCurrentItem != null) {
398            mCurrentItem.setSelected(false);
399        }
400        if (item != null) {
401            // clear up stack
402            playSoundEffect(SoundEffectConstants.CLICK);
403            item.setSelected(true);
404            mPieView = null;
405        }
406        mCurrentItem = item;
407    }
408
409    private void deselect() {
410        if (mCurrentItem != null) {
411            mCurrentItem.setSelected(false);
412        }
413        mCurrentItem = null;
414        mPieView = null;
415    }
416
417    private PointF getPolar(float x, float y) {
418        PointF res = new PointF();
419        // get angle and radius from x/y
420        res.x = (float) Math.PI / 2;
421        x = mCenter.x - x;
422        if (mCenter.x < mSlop) {
423            x = -x;
424        }
425        y = mCenter.y - y;
426        res.y = (float) Math.sqrt(x * x + y * y);
427        if (y > 0) {
428            res.x = (float) Math.asin(x / res.y);
429        } else if (y < 0) {
430            res.x = (float) (Math.PI - Math.asin(x / res.y ));
431        }
432        return res;
433    }
434
435    /**
436     *
437     * @param polar x: angle, y: dist
438     * @return the item at angle/dist or null
439     */
440    private PieItem findItem(PointF polar) {
441        // find the matching item:
442        for (PieItem item : mItems) {
443            if ((item.getInnerRadius() - mTouchOffset < polar.y)
444                    && (item.getOuterRadius() - mTouchOffset > polar.y)
445                    && (item.getStartAngle() < polar.x)
446                    && (item.getStartAngle() + item.getSweep() > polar.x)) {
447                return item;
448            }
449        }
450        return null;
451    }
452
453}
454