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