PieMenu.java revision a4befac241ec69791fd30e11355ed3dc10d7fb37
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
63a4befac241ec69791fd30e11355ed3dc10d7fb37Michael Kolb        public void layout(int anchorX, int anchorY, boolean onleft, float angle,
64a4befac241ec69791fd30e11355ed3dc10d7fb37Michael Kolb                int parentHeight);
651acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb
661acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb        public void draw(Canvas c);
671acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb
681acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb        public boolean onTouchEvent(MotionEvent evt);
691acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb
701acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb    }
711acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb
72376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private Point mCenter;
73376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private int mRadius;
74376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private int mRadiusInc;
75376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private int mSlop;
76565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb    private int mTouchOffset;
77376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
78376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private boolean mOpen;
79376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private PieController mController;
80376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
810860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb    private List<PieItem> mItems;
820860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb    private int mLevels;
830860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb    private int[] mCounts;
841acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb    private PieView mPieView = null;
852a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb
860860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb    private Drawable mBackground;
87565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb    private Paint mNormalPaint;
88565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb    private Paint mSelectedPaint;
892a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb
900860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb    // touch handling
910860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb    PieItem mCurrentItem;
922a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb
93716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb    private boolean mUseBackground;
94716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb
95376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    /**
96376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param context
97376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param attrs
98376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param defStyle
99376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     */
100376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    public PieMenu(Context context, AttributeSet attrs, int defStyle) {
101376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        super(context, attrs, defStyle);
102376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        init(context);
103376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
104376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
105376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    /**
106376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param context
107376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param attrs
108376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     */
109376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    public PieMenu(Context context, AttributeSet attrs) {
110376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        super(context, attrs);
111376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        init(context);
112376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
113376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
114376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    /**
115376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * @param context
116376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     */
117376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    public PieMenu(Context context) {
118376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        super(context);
119376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        init(context);
120376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
121376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
122376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private void init(Context ctx) {
1230860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        mItems = new ArrayList<PieItem>();
1240860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        mLevels = 0;
1250860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        mCounts = new int[MAX_LEVELS];
126376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        Resources res = ctx.getResources();
1270860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        mRadius = (int) res.getDimension(R.dimen.qc_radius_start);
1280860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        mRadiusInc = (int) res.getDimension(R.dimen.qc_radius_increment);
129376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mSlop = (int) res.getDimension(R.dimen.qc_slop);
130565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        mTouchOffset = (int) res.getDimension(R.dimen.qc_touch_offset);
131376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mOpen = false;
132376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        setWillNotDraw(false);
133376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        setDrawingCacheEnabled(false);
134376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mCenter = new Point(0,0);
1350860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        mBackground = res.getDrawable(R.drawable.qc_background_normal);
136565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        mNormalPaint = new Paint();
137565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        mNormalPaint.setColor(res.getColor(R.color.qc_normal));
138565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        mNormalPaint.setAntiAlias(true);
139565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        mSelectedPaint = new Paint();
140565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        mSelectedPaint.setColor(res.getColor(R.color.qc_selected));
141565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        mSelectedPaint.setAntiAlias(true);
142376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
143376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
144376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    public void setController(PieController ctl) {
145376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mController = ctl;
146376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
147376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
148716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb    public void setUseBackground(boolean useBackground) {
149716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb        mUseBackground = useBackground;
150716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb    }
151716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb
1520860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb    public void addItem(PieItem item) {
153376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        // add the item to the pie itself
1540860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        mItems.add(item);
1550860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        int l = item.getLevel();
1560860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        mLevels = Math.max(mLevels, l);
1570860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        mCounts[l]++;
158376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
159376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
1600860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb    public void removeItem(PieItem item) {
1610860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        mItems.remove(item);
162376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
163376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
164376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    public void clearItems() {
1650860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        mItems.clear();
166376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
167376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
1680860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb    private boolean onTheLeft() {
1690860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        return mCenter.x < mSlop;
1700860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb    }
171376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
1720860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb    /**
1730860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb     * guaranteed has center set
1740860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb     * @param show
1750860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb     */
1760860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb    private void show(boolean show) {
177376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        mOpen = show;
178376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        if (mOpen) {
179376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            if (mController != null) {
180376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                boolean changed = mController.onOpen();
181376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
1820860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb            layoutPie();
183376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
184376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        if (!show) {
1850860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb            mCurrentItem = null;
1861acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb            mPieView = null;
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
2000860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb    private void layoutPie() {
201565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        float emptyangle = (float) Math.PI / 16;
202565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        int rgap = 2;
203565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        int inner = mRadius + rgap;
204565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        int outer = mRadius + mRadiusInc - rgap;
2051acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb        int radius = mRadius;
206565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        int gap = 1;
2070860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        for (int i = 0; i < mLevels; i++) {
2080860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb            int level = i + 1;
209565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb            float sweep = (float) (Math.PI - 2 * emptyangle) / mCounts[level];
210565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb            float angle = emptyangle + sweep / 2;
2110860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb            for (PieItem item : mItems) {
2120860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb                if (item.getLevel() == level) {
2130860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb                    View view = item.getView();
2140860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb                    view.measure(view.getLayoutParams().width,
2150860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb                            view.getLayoutParams().height);
2160860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb                    int w = view.getMeasuredWidth();
2170860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb                    int h = view.getMeasuredHeight();
218565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                    int r = inner + (outer - inner) * 2 / 3;
219565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                    int x = (int) (r * Math.sin(angle));
220565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                    int y = mCenter.y - (int) (r * Math.cos(angle)) - h / 2;
2210860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb                    if (onTheLeft()) {
222565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                        x = mCenter.x + x - w / 2;
2230860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb                    } else {
224565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                        x = mCenter.x - x - w / 2;
2250860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb                    }
2260860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb                    view.layout(x, y, x + w, y + h);
2270860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb                    float itemstart = angle - sweep / 2;
228565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                    Path slice = makeSlice(getDegrees(itemstart) - gap,
229565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                            getDegrees(itemstart + sweep) + gap,
230565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                            outer, inner, mCenter);
231565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                    item.setGeometry(itemstart, sweep, inner, outer, slice);
2320860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb                    angle += sweep;
233376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                }
234376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
2350860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb            inner += mRadiusInc;
2360860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb            outer += mRadiusInc;
237376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
238376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
239376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
240565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb
241565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb    /**
242565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb     * converts a
243565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb     *
244565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb     * @param angle from 0..PI to Android degrees (clockwise starting at 3
245565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb     *        o'clock)
246565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb     * @return skia angle
247565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb     */
248565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb    private float getDegrees(double angle) {
249565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        return (float) (270 - 180 * angle / Math.PI);
250565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb    }
251565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb
2520860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb    @Override
2530860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb    protected void onDraw(Canvas canvas) {
2540860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        if (mOpen) {
255565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb            int state;
256716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb            if (mUseBackground) {
257716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb                int w = mBackground.getIntrinsicWidth();
258716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb                int h = mBackground.getIntrinsicHeight();
259716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb                int left = mCenter.x - w;
260716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb                int top = mCenter.y - h / 2;
261716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb                mBackground.setBounds(left, top, left + w, top + h);
262716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb                state = canvas.save();
263716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb                if (onTheLeft()) {
264716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb                    canvas.scale(-1, 1);
265716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb                }
266716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb                mBackground.draw(canvas);
267716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb                canvas.restoreToCount(state);
268376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
2690860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb            for (PieItem item : mItems) {
270565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                Paint p = item.isSelected() ? mSelectedPaint : mNormalPaint;
271565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                state = canvas.save();
272565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                if (onTheLeft()) {
273565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                    canvas.scale(-1, 1);
274565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                }
275565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                drawPath(canvas, item.getPath(), p);
276565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                canvas.restoreToCount(state);
2770860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb                drawItem(canvas, item);
278376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
2791acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb            if (mPieView != null) {
2801acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb                mPieView.draw(canvas);
2811acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb            }
282376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
2832a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb    }
2842a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb
2850860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb    private void drawItem(Canvas canvas, PieItem item) {
2860860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        int outer = item.getOuterRadius();
2870860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        int left = mCenter.x - outer;
2880860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        int top = mCenter.y - outer;
2890860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        // draw the item view
2900860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        View view = item.getView();
2910860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        int state = canvas.save();
2920860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        canvas.translate(view.getX(), view.getY());
2930860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        view.draw(canvas);
2940860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        canvas.restoreToCount(state);
295376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
296376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
297565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb    private void drawPath(Canvas canvas, Path path, Paint paint) {
298565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        canvas.drawPath(path, paint);
299565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb    }
300565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb
301565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb    private Path makeSlice(float start, float end, int outer, int inner, Point center) {
302565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        RectF bb =
303565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                new RectF(center.x - outer, center.y - outer, center.x + outer,
304565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                        center.y + outer);
305565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        RectF bbi =
306565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                new RectF(center.x - inner, center.y - inner, center.x + inner,
307565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                        center.y + inner);
308565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        Path path = new Path();
309565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        path.arcTo(bb, start, end - start, true);
310565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        path.arcTo(bbi, end, start - end);
311565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        path.close();
312565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb        return path;
313565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb    }
314565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb
315376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    // touch handling for pie
316376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
317376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    @Override
318376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    public boolean onTouchEvent(MotionEvent evt) {
319376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        float x = evt.getX();
320376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        float y = evt.getY();
321376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        int action = evt.getActionMasked();
322376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        if (MotionEvent.ACTION_DOWN == action) {
323376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            if ((x > getWidth() - mSlop) || (x < mSlop)) {
324376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                setCenter((int) x, (int) y);
325376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                show(true);
326376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                return true;
327376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
328376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        } else if (MotionEvent.ACTION_UP == action) {
329376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            if (mOpen) {
3301acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb                boolean handled = false;
3311acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb                if (mPieView != null) {
3321acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb                    handled = mPieView.onTouchEvent(evt);
3331acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb                }
3340860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb                PieItem item = mCurrentItem;
335376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                deselect();
336376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                show(false);
3371acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb                if (!handled && (item != null)) {
3380860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb                    item.getView().performClick();
3390860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb                }
340376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                return true;
341376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
342376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        } else if (MotionEvent.ACTION_CANCEL == action) {
343376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            if (mOpen) {
344376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                show(false);
345376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
346376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            deselect();
347376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            return false;
348376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        } else if (MotionEvent.ACTION_MOVE == action) {
3490860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb            boolean handled = false;
350bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb            PointF polar = getPolar(x, y);
3510860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb            int maxr = mRadius + mLevels * mRadiusInc + 50;
3521acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb            if (mPieView != null) {
3531acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb                handled = mPieView.onTouchEvent(evt);
3541acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb            }
3551acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb            if (handled) {
3561acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb                invalidate();
3571acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb                return false;
3581acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb            }
3590860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb            if (polar.y > maxr) {
360bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb                deselect();
3610860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb                show(false);
3624be9bc7f7f38723ae8c4ca1d3203de212cf214bdJohn Reck                evt.setAction(MotionEvent.ACTION_DOWN);
3634be9bc7f7f38723ae8c4ca1d3203de212cf214bdJohn Reck                if (getParent() != null) {
3644be9bc7f7f38723ae8c4ca1d3203de212cf214bdJohn Reck                    ((ViewGroup) getParent()).dispatchTouchEvent(evt);
3654be9bc7f7f38723ae8c4ca1d3203de212cf214bdJohn Reck                }
366bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb                return false;
367bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb            }
3680860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb            PieItem item = findItem(polar);
3690860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb            if (mCurrentItem != item) {
3700860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb                onEnter(item);
3711acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb                if ((item != null) && item.isPieView()) {
3721acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb                    int cx = item.getView().getLeft() + (onTheLeft()
3731acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb                            ? item.getView().getWidth() : 0);
3741acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb                    int cy = item.getView().getTop();
3751acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb                    mPieView = item.getPieView();
376565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                    layoutPieView(mPieView, cx, cy,
377565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                            (item.getStartAngle() + item.getSweep()) / 2);
3781acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb                }
379376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb                invalidate();
380376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
381376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
382376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        // always re-dispatch event
383376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        return false;
384376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
385376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
386565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb    private void layoutPieView(PieView pv, int x, int y, float angle) {
387a4befac241ec69791fd30e11355ed3dc10d7fb37Michael Kolb        pv.layout(x, y, onTheLeft(), angle, getHeight());
3881acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb    }
3891acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb
390376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    /**
391376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * enter a slice for a view
392376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     * updates model only
3930860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb     * @param item
394376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb     */
3950860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb    private void onEnter(PieItem item) {
396376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        // deselect
3970860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        if (mCurrentItem != null) {
3980860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb            mCurrentItem.setSelected(false);
399376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
4000860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        if (item != null) {
401376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            // clear up stack
4022a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb            playSoundEffect(SoundEffectConstants.CLICK);
4030860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb            item.setSelected(true);
4041acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb            mPieView = null;
405376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
4060860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        mCurrentItem = item;
407376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
408376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
409376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    private void deselect() {
4100860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        if (mCurrentItem != null) {
4110860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb            mCurrentItem.setSelected(false);
412376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
4130860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        mCurrentItem = null;
4141acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb        mPieView = null;
415376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
416376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
417bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb    private PointF getPolar(float x, float y) {
418bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb        PointF res = new PointF();
419376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        // get angle and radius from x/y
420bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb        res.x = (float) Math.PI / 2;
421376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        x = mCenter.x - x;
422376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        if (mCenter.x < mSlop) {
423376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            x = -x;
424376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
425376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        y = mCenter.y - y;
426bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb        res.y = (float) Math.sqrt(x * x + y * y);
427376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        if (y > 0) {
428bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb            res.x = (float) Math.asin(x / res.y);
429376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        } else if (y < 0) {
430bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb            res.x = (float) (Math.PI - Math.asin(x / res.y ));
431376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
432bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb        return res;
433bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb    }
434bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb
435bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb    /**
436bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb     *
437bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb     * @param polar x: angle, y: dist
4380860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb     * @return the item at angle/dist or null
439bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb     */
4400860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb    private PieItem findItem(PointF polar) {
441376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        // find the matching item:
4420860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb        for (PieItem item : mItems) {
443565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb            if ((item.getInnerRadius() - mTouchOffset < polar.y)
444565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb                    && (item.getOuterRadius() - mTouchOffset > polar.y)
4450860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb                    && (item.getStartAngle() < polar.x)
4460860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb                    && (item.getStartAngle() + item.getSweep() > polar.x)) {
4470860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb                return item;
448376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb            }
449376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        }
450376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb        return null;
451376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb    }
452376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb
453376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb}
454