PieMenu.java revision 716773853705e67fe5d5afbf9c3b917ff8a5e298
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;
24565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolbimport android.graphics.Paint;
25565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolbimport android.graphics.Path;
26376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport android.graphics.Point;
27376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport android.graphics.PointF;
28565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolbimport android.graphics.RectF;
292a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolbimport android.graphics.drawable.Drawable;
30376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport android.util.AttributeSet;
31376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport android.view.MotionEvent;
322a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolbimport android.view.SoundEffectConstants;
33376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport android.view.View;
344be9bc7f7f38723ae8c4ca1d3203de212cf214bdJohn Reckimport android.view.ViewGroup;
35376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport android.widget.FrameLayout;
36376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
37376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport java.util.ArrayList;
38376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport java.util.List;
39376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
40376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbpublic class PieMenu extends FrameLayout {
41376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
420860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb    private static final int MAX_LEVELS = 5;
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    }
510860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb
521acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb    /**
531acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb     * A view like object that lives off of the pie menu
541acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb     */
551acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb    public interface PieView {
561acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb
57eb95db48b01b3db935601f25bd1a2358674b76daMichael Kolb        public interface OnLayoutListener {
58eb95db48b01b3db935601f25bd1a2358674b76daMichael Kolb            public void onLayout(int ax, int ay, boolean left);
59eb95db48b01b3db935601f25bd1a2358674b76daMichael Kolb        }
60eb95db48b01b3db935601f25bd1a2358674b76daMichael Kolb
61eb95db48b01b3db935601f25bd1a2358674b76daMichael Kolb        public void setLayoutListener(OnLayoutListener l);
62eb95db48b01b3db935601f25bd1a2358674b76daMichael Kolb
63565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        public void layout(int anchorX, int anchorY, boolean onleft, float angle);
641acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb
651acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb        public void draw(Canvas c);
661acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb
671acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb        public boolean onTouchEvent(MotionEvent evt);
681acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb
691acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb    }
701acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb
71376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private Point mCenter;
72376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private int mRadius;
73376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private int mRadiusInc;
74376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private int mSlop;
75565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb    private int mTouchOffset;
76376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
77376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private boolean mOpen;
78376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private PieController mController;
79376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
800860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb    private List<PieItem> mItems;
810860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb    private int mLevels;
820860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb    private int[] mCounts;
831acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb    private PieView mPieView = null;
842a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb
850860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb    private Drawable mBackground;
86565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb    private Paint mNormalPaint;
87565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb    private Paint mSelectedPaint;
882a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb
890860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb    // touch handling
900860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb    PieItem mCurrentItem;
912a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb
92716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb    private boolean mUseBackground;
93716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb
94376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    /**
95376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param context
96376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param attrs
97376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param defStyle
98376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     */
99376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    public PieMenu(Context context, AttributeSet attrs, int defStyle) {
100376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        super(context, attrs, defStyle);
101376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        init(context);
102376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
103376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
104376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    /**
105376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param context
106376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param attrs
107376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     */
108376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    public PieMenu(Context context, AttributeSet attrs) {
109376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        super(context, attrs);
110376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        init(context);
111376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
112376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
113376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    /**
114376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param context
115376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     */
116376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    public PieMenu(Context context) {
117376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        super(context);
118376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        init(context);
119376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
120376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
121376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private void init(Context ctx) {
1220860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        mItems = new ArrayList<PieItem>();
1230860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        mLevels = 0;
1240860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        mCounts = new int[MAX_LEVELS];
125376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        Resources res = ctx.getResources();
1260860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        mRadius = (int) res.getDimension(R.dimen.qc_radius_start);
1270860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        mRadiusInc = (int) res.getDimension(R.dimen.qc_radius_increment);
128376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mSlop = (int) res.getDimension(R.dimen.qc_slop);
129565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        mTouchOffset = (int) res.getDimension(R.dimen.qc_touch_offset);
130376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mOpen = false;
131376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        setWillNotDraw(false);
132376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        setDrawingCacheEnabled(false);
133376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mCenter = new Point(0,0);
1340860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        mBackground = res.getDrawable(R.drawable.qc_background_normal);
135565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        mNormalPaint = new Paint();
136565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        mNormalPaint.setColor(res.getColor(R.color.qc_normal));
137565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        mNormalPaint.setAntiAlias(true);
138565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        mSelectedPaint = new Paint();
139565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        mSelectedPaint.setColor(res.getColor(R.color.qc_selected));
140565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        mSelectedPaint.setAntiAlias(true);
141376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
142376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
143376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    public void setController(PieController ctl) {
144376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mController = ctl;
145376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
146376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
147716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb    public void setUseBackground(boolean useBackground) {
148716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb        mUseBackground = useBackground;
149716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb    }
150716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb
1510860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb    public void addItem(PieItem item) {
152376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        // add the item to the pie itself
1530860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        mItems.add(item);
1540860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        int l = item.getLevel();
1550860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        mLevels = Math.max(mLevels, l);
1560860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        mCounts[l]++;
157376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
158376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
1590860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb    public void removeItem(PieItem item) {
1600860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        mItems.remove(item);
161376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
162376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
163376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    public void clearItems() {
1640860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        mItems.clear();
165376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
166376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
1670860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb    private boolean onTheLeft() {
1680860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        return mCenter.x < mSlop;
1690860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb    }
170376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
1710860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb    /**
1720860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb     * guaranteed has center set
1730860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb     * @param show
1740860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb     */
1750860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb    private 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            }
1810860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb            layoutPie();
182376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
183376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        if (!show) {
1840860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb            mCurrentItem = null;
1851acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb            mPieView = null;
186376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
187376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        invalidate();
188376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
189376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
190376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private void setCenter(int x, int y) {
191376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        if (x < mSlop) {
192376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            mCenter.x = 0;
193376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        } else {
194376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            mCenter.x = getWidth();
195376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
196376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mCenter.y = y;
197376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
198376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
1990860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb    private void layoutPie() {
200565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        float emptyangle = (float) Math.PI / 16;
201565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        int rgap = 2;
202565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        int inner = mRadius + rgap;
203565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        int outer = mRadius + mRadiusInc - rgap;
2041acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb        int radius = mRadius;
205565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        int gap = 1;
2060860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        for (int i = 0; i < mLevels; i++) {
2070860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb            int level = i + 1;
208565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb            float sweep = (float) (Math.PI - 2 * emptyangle) / mCounts[level];
209565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb            float angle = emptyangle + sweep / 2;
2100860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb            for (PieItem item : mItems) {
2110860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb                if (item.getLevel() == level) {
2120860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb                    View view = item.getView();
2130860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb                    view.measure(view.getLayoutParams().width,
2140860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb                            view.getLayoutParams().height);
2150860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb                    int w = view.getMeasuredWidth();
2160860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb                    int h = view.getMeasuredHeight();
217565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                    int r = inner + (outer - inner) * 2 / 3;
218565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                    int x = (int) (r * Math.sin(angle));
219565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                    int y = mCenter.y - (int) (r * Math.cos(angle)) - h / 2;
2200860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb                    if (onTheLeft()) {
221565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                        x = mCenter.x + x - w / 2;
2220860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb                    } else {
223565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                        x = mCenter.x - x - w / 2;
2240860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb                    }
2250860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb                    view.layout(x, y, x + w, y + h);
2260860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb                    float itemstart = angle - sweep / 2;
227565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                    Path slice = makeSlice(getDegrees(itemstart) - gap,
228565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                            getDegrees(itemstart + sweep) + gap,
229565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                            outer, inner, mCenter);
230565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                    item.setGeometry(itemstart, sweep, inner, outer, slice);
2310860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb                    angle += sweep;
232376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                }
233376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
2340860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb            inner += mRadiusInc;
2350860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb            outer += mRadiusInc;
236376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
237376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
238376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
239565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb
240565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb    /**
241565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb     * converts a
242565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb     *
243565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb     * @param angle from 0..PI to Android degrees (clockwise starting at 3
244565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb     *        o'clock)
245565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb     * @return skia angle
246565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb     */
247565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb    private float getDegrees(double angle) {
248565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        return (float) (270 - 180 * angle / Math.PI);
249565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb    }
250565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb
2510860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb    @Override
2520860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb    protected void onDraw(Canvas canvas) {
2530860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        if (mOpen) {
254565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb            int state;
255716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb            if (mUseBackground) {
256716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb                int w = mBackground.getIntrinsicWidth();
257716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb                int h = mBackground.getIntrinsicHeight();
258716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb                int left = mCenter.x - w;
259716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb                int top = mCenter.y - h / 2;
260716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb                mBackground.setBounds(left, top, left + w, top + h);
261716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb                state = canvas.save();
262716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb                if (onTheLeft()) {
263716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb                    canvas.scale(-1, 1);
264716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb                }
265716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb                mBackground.draw(canvas);
266716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb                canvas.restoreToCount(state);
267376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
2680860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb            for (PieItem item : mItems) {
269565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                Paint p = item.isSelected() ? mSelectedPaint : mNormalPaint;
270565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                state = canvas.save();
271565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                if (onTheLeft()) {
272565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                    canvas.scale(-1, 1);
273565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                }
274565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                drawPath(canvas, item.getPath(), p);
275565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                canvas.restoreToCount(state);
2760860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb                drawItem(canvas, item);
277376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
2781acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb            if (mPieView != null) {
2791acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb                mPieView.draw(canvas);
2801acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb            }
281376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
2822a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb    }
2832a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb
2840860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb    private void drawItem(Canvas canvas, PieItem item) {
2850860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        int outer = item.getOuterRadius();
2860860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        int left = mCenter.x - outer;
2870860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        int top = mCenter.y - outer;
2880860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        // draw the item view
2890860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        View view = item.getView();
2900860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        int state = canvas.save();
2910860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        canvas.translate(view.getX(), view.getY());
2920860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        view.draw(canvas);
2930860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        canvas.restoreToCount(state);
294376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
295376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
296565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb    private void drawPath(Canvas canvas, Path path, Paint paint) {
297565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        canvas.drawPath(path, paint);
298565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb    }
299565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb
300565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb    private Path makeSlice(float start, float end, int outer, int inner, Point center) {
301565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        RectF bb =
302565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                new RectF(center.x - outer, center.y - outer, center.x + outer,
303565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                        center.y + outer);
304565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        RectF bbi =
305565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                new RectF(center.x - inner, center.y - inner, center.x + inner,
306565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                        center.y + inner);
307565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        Path path = new Path();
308565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        path.arcTo(bb, start, end - start, true);
309565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        path.arcTo(bbi, end, start - end);
310565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        path.close();
311565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        return path;
312565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb    }
313565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb
314376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    // touch handling for pie
315376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
316376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    @Override
317376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    public boolean onTouchEvent(MotionEvent evt) {
318376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        float x = evt.getX();
319376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        float y = evt.getY();
320376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        int action = evt.getActionMasked();
321376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        if (MotionEvent.ACTION_DOWN == action) {
322376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            if ((x > getWidth() - mSlop) || (x < mSlop)) {
323376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                setCenter((int) x, (int) y);
324376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                show(true);
325376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                return true;
326376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
327376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        } else if (MotionEvent.ACTION_UP == action) {
328376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            if (mOpen) {
3291acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb                boolean handled = false;
3301acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb                if (mPieView != null) {
3311acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb                    handled = mPieView.onTouchEvent(evt);
3321acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb                }
3330860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb                PieItem item = mCurrentItem;
334376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                deselect();
335376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                show(false);
3361acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb                if (!handled && (item != null)) {
3370860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb                    item.getView().performClick();
3380860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb                }
339376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                return true;
340376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
341376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        } else if (MotionEvent.ACTION_CANCEL == action) {
342376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            if (mOpen) {
343376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                show(false);
344376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
345376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            deselect();
346376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            return false;
347376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        } else if (MotionEvent.ACTION_MOVE == action) {
3480860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb            boolean handled = false;
349bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb            PointF polar = getPolar(x, y);
3500860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb            int maxr = mRadius + mLevels * mRadiusInc + 50;
3511acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb            if (mPieView != null) {
3521acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb                handled = mPieView.onTouchEvent(evt);
3531acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb            }
3541acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb            if (handled) {
3551acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb                invalidate();
3561acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb                return false;
3571acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb            }
3580860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb            if (polar.y > maxr) {
359bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb                deselect();
3600860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb                show(false);
3614be9bc7f7f38723ae8c4ca1d3203de212cf214bdJohn Reck                evt.setAction(MotionEvent.ACTION_DOWN);
3624be9bc7f7f38723ae8c4ca1d3203de212cf214bdJohn Reck                if (getParent() != null) {
3634be9bc7f7f38723ae8c4ca1d3203de212cf214bdJohn Reck                    ((ViewGroup) getParent()).dispatchTouchEvent(evt);
3644be9bc7f7f38723ae8c4ca1d3203de212cf214bdJohn Reck                }
365bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb                return false;
366bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb            }
3670860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb            PieItem item = findItem(polar);
3680860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb            if (mCurrentItem != item) {
3690860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb                onEnter(item);
3701acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb                if ((item != null) && item.isPieView()) {
3711acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb                    int cx = item.getView().getLeft() + (onTheLeft()
3721acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb                            ? item.getView().getWidth() : 0);
3731acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb                    int cy = item.getView().getTop();
3741acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb                    mPieView = item.getPieView();
375565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                    layoutPieView(mPieView, cx, cy,
376565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                            (item.getStartAngle() + item.getSweep()) / 2);
3771acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb                }
378376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                invalidate();
379376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
380376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
381376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        // always re-dispatch event
382376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        return false;
383376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
384376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
385565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb    private void layoutPieView(PieView pv, int x, int y, float angle) {
386565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        pv.layout(x, y, onTheLeft(), angle);
3871acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb    }
3881acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb
389376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    /**
390376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * enter a slice for a view
391376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * updates model only
3920860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb     * @param item
393376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     */
3940860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb    private void onEnter(PieItem item) {
395376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        // deselect
3960860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        if (mCurrentItem != null) {
3970860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb            mCurrentItem.setSelected(false);
398376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
3990860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        if (item != null) {
400376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            // clear up stack
4012a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb            playSoundEffect(SoundEffectConstants.CLICK);
4020860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb            item.setSelected(true);
4031acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb            mPieView = null;
404376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
4050860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        mCurrentItem = item;
406376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
407376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
408376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private void deselect() {
4090860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        if (mCurrentItem != null) {
4100860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb            mCurrentItem.setSelected(false);
411376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
4120860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        mCurrentItem = null;
4131acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb        mPieView = null;
414376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
415376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
416bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb    private PointF getPolar(float x, float y) {
417bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb        PointF res = new PointF();
418376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        // get angle and radius from x/y
419bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb        res.x = (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;
425bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb        res.y = (float) Math.sqrt(x * x + y * y);
426376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        if (y > 0) {
427bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb            res.x = (float) Math.asin(x / res.y);
428376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        } else if (y < 0) {
429bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb            res.x = (float) (Math.PI - Math.asin(x / res.y ));
430376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
431bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb        return res;
432bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb    }
433bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb
434bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb    /**
435bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb     *
436bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb     * @param polar x: angle, y: dist
4370860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb     * @return the item at angle/dist or null
438bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb     */
4390860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb    private PieItem findItem(PointF polar) {
440376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        // find the matching item:
4410860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        for (PieItem item : mItems) {
442565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb            if ((item.getInnerRadius() - mTouchOffset < polar.y)
443565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                    && (item.getOuterRadius() - mTouchOffset > polar.y)
4440860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb                    && (item.getStartAngle() < polar.x)
4450860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb                    && (item.getStartAngle() + item.getSweep() > polar.x)) {
4460860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb                return item;
447376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
448376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
449376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        return null;
450376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
451376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
452376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb}
453