blob: 935b5f662493034d87dd08480f3e2bc36fdd8333 [file] [log] [blame]
Michael Kolb8872c232013-01-29 10:33:22 -08001/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.camera.ui;
18
Doris Liu1b9362e2013-07-11 16:32:13 -070019import android.animation.Animator;
20import android.animation.Animator.AnimatorListener;
21import android.animation.ValueAnimator;
Michael Kolb8872c232013-01-29 10:33:22 -080022import android.content.Context;
23import android.content.res.Resources;
24import android.graphics.Canvas;
25import android.graphics.Color;
26import android.graphics.Paint;
27import android.graphics.Path;
28import android.graphics.Point;
29import android.graphics.PointF;
30import android.graphics.RectF;
31import android.os.Handler;
32import android.os.Message;
Michael Kolb0f4a5ae2013-04-10 14:16:19 -070033import android.util.FloatMath;
Michael Kolb8872c232013-01-29 10:33:22 -080034import android.view.MotionEvent;
35import android.view.ViewConfiguration;
36import android.view.animation.Animation;
Michael Kolb8872c232013-01-29 10:33:22 -080037import android.view.animation.Transformation;
38
Angus Kong2bca2102014-03-11 16:27:30 -070039import com.android.camera.debug.Log;
Michael Kolb10f4ba02013-04-10 08:50:51 -070040import com.android.camera.drawable.TextDrawable;
Sascha Haeberling8e963a52013-08-06 11:43:02 -070041import com.android.camera2.R;
Michael Kolb8872c232013-01-29 10:33:22 -080042
Sascha Haeberlingb7b73ec2013-11-12 15:16:08 -080043import java.util.ArrayList;
44import java.util.List;
45
Sascha Haeberlinge0ecc642013-11-07 13:43:33 -080046/**
47 * An overlay renderer that is used to display focus state and progress state.
48 */
Michael Kolb8872c232013-01-29 10:33:22 -080049public class PieRenderer extends OverlayRenderer
50 implements FocusIndicator {
51
Angus Kong2bca2102014-03-11 16:27:30 -070052 private static final Log.Tag TAG = new Log.Tag("PieRenderer");
Michael Kolb8872c232013-01-29 10:33:22 -080053
54 // Sometimes continuous autofocus starts and stops several times quickly.
55 // These states are used to make sure the animation is run for at least some
56 // time.
57 private volatile int mState;
58 private ScaleAnimation mAnimation = new ScaleAnimation();
59 private static final int STATE_IDLE = 0;
60 private static final int STATE_FOCUSING = 1;
61 private static final int STATE_FINISHING = 2;
62 private static final int STATE_PIE = 8;
63
Michael Kolb3bc96b22013-03-12 10:24:42 -070064 private static final float MATH_PI_2 = (float)(Math.PI / 2);
65
Michael Kolb8872c232013-01-29 10:33:22 -080066 private Runnable mDisappear = new Disappear();
67 private Animation.AnimationListener mEndAction = new EndAction();
68 private static final int SCALING_UP_TIME = 600;
69 private static final int SCALING_DOWN_TIME = 100;
70 private static final int DISAPPEAR_TIMEOUT = 200;
71 private static final int DIAL_HORIZONTAL = 157;
Michael Kolbebcf6fe2013-02-27 11:00:31 -080072 // fade out timings
73 private static final int PIE_FADE_OUT_DURATION = 600;
Michael Kolb8872c232013-01-29 10:33:22 -080074
75 private static final long PIE_FADE_IN_DURATION = 200;
76 private static final long PIE_XFADE_DURATION = 200;
77 private static final long PIE_SELECT_FADE_DURATION = 300;
Michael Kolb3bc96b22013-03-12 10:24:42 -070078 private static final long PIE_OPEN_SUB_DELAY = 400;
Michael Kolbad2a7452013-05-07 13:53:19 -070079 private static final long PIE_SLICE_DURATION = 80;
Michael Kolb8872c232013-01-29 10:33:22 -080080
81 private static final int MSG_OPEN = 0;
82 private static final int MSG_CLOSE = 1;
Michael Kolb3bc96b22013-03-12 10:24:42 -070083 private static final int MSG_OPENSUBMENU = 2;
Michael Kolb3daa3512013-04-02 16:03:04 -070084
85 protected static float CENTER = (float) Math.PI / 2;
Michael Kolbf4651102013-04-19 16:21:24 -070086 protected static float RAD24 = (float)(24 * Math.PI / 180);
Michael Kolb3daa3512013-04-02 16:03:04 -070087 protected static final float SWEEP_SLICE = 0.14f;
88 protected static final float SWEEP_ARC = 0.23f;
89
Michael Kolb8872c232013-01-29 10:33:22 -080090 // geometry
Michael Kolb8872c232013-01-29 10:33:22 -080091 private int mRadius;
Michael Kolb3daa3512013-04-02 16:03:04 -070092 private int mRadiusInc;
Michael Kolb8872c232013-01-29 10:33:22 -080093
94 // the detection if touch is inside a slice is offset
95 // inbounds by this amount to allow the selection to show before the
96 // finger covers it
97 private int mTouchOffset;
98
Michael Kolb3bc96b22013-03-12 10:24:42 -070099 private List<PieItem> mOpen;
Michael Kolb8872c232013-01-29 10:33:22 -0800100
101 private Paint mSelectedPaint;
102 private Paint mSubPaint;
Michael Kolb3bc96b22013-03-12 10:24:42 -0700103 private Paint mMenuArcPaint;
Michael Kolb8872c232013-01-29 10:33:22 -0800104
105 // touch handling
106 private PieItem mCurrentItem;
107
108 private Paint mFocusPaint;
109 private int mSuccessColor;
110 private int mFailColor;
111 private int mCircleSize;
112 private int mFocusX;
113 private int mFocusY;
114 private int mCenterX;
115 private int mCenterY;
Michael Kolb3daa3512013-04-02 16:03:04 -0700116 private int mArcCenterY;
117 private int mSliceCenterY;
Michael Kolb3bc96b22013-03-12 10:24:42 -0700118 private int mPieCenterX;
119 private int mPieCenterY;
Michael Kolb3daa3512013-04-02 16:03:04 -0700120 private int mSliceRadius;
Michael Kolb3bc96b22013-03-12 10:24:42 -0700121 private int mArcRadius;
122 private int mArcOffset;
Michael Kolb8872c232013-01-29 10:33:22 -0800123
124 private int mDialAngle;
125 private RectF mCircle;
126 private RectF mDial;
127 private Point mPoint1;
128 private Point mPoint2;
129 private int mStartAnimationAngle;
130 private boolean mFocused;
131 private int mInnerOffset;
132 private int mOuterStroke;
133 private int mInnerStroke;
134 private boolean mTapMode;
135 private boolean mBlockFocus;
136 private int mTouchSlopSquared;
137 private Point mDown;
138 private boolean mOpening;
Doris Liu1b9362e2013-07-11 16:32:13 -0700139 private ValueAnimator mXFade;
140 private ValueAnimator mFadeIn;
141 private ValueAnimator mFadeOut;
142 private ValueAnimator mSlice;
Michael Kolb8872c232013-01-29 10:33:22 -0800143 private volatile boolean mFocusCancelled;
Michael Kolb3daa3512013-04-02 16:03:04 -0700144 private PointF mPolar = new PointF();
Michael Kolb10f4ba02013-04-10 08:50:51 -0700145 private TextDrawable mLabel;
Michael Kolb0f4a5ae2013-04-10 14:16:19 -0700146 private int mDeadZone;
147 private int mAngleZone;
148 private float mCenterAngle;
Michael Kolb3daa3512013-04-02 16:03:04 -0700149
Michael Kolb8872c232013-01-29 10:33:22 -0800150 private Handler mHandler = new Handler() {
151 public void handleMessage(Message msg) {
152 switch(msg.what) {
153 case MSG_OPEN:
154 if (mListener != null) {
Michael Kolb3bc96b22013-03-12 10:24:42 -0700155 mListener.onPieOpened(mPieCenterX, mPieCenterY);
Michael Kolb8872c232013-01-29 10:33:22 -0800156 }
157 break;
158 case MSG_CLOSE:
159 if (mListener != null) {
160 mListener.onPieClosed();
161 }
162 break;
Michael Kolb3bc96b22013-03-12 10:24:42 -0700163 case MSG_OPENSUBMENU:
164 onEnterOpen();
165 break;
Michael Kolb8872c232013-01-29 10:33:22 -0800166 }
Michael Kolb3bc96b22013-03-12 10:24:42 -0700167
Michael Kolb8872c232013-01-29 10:33:22 -0800168 }
169 };
170
171 private PieListener mListener;
172
173 static public interface PieListener {
174 public void onPieOpened(int centerX, int centerY);
175 public void onPieClosed();
176 }
177
178 public void setPieListener(PieListener pl) {
179 mListener = pl;
180 }
181
182 public PieRenderer(Context context) {
183 init(context);
184 }
185
186 private void init(Context ctx) {
187 setVisible(false);
Michael Kolb3bc96b22013-03-12 10:24:42 -0700188 mOpen = new ArrayList<PieItem>();
189 mOpen.add(new PieItem(null, 0));
Michael Kolb8872c232013-01-29 10:33:22 -0800190 Resources res = ctx.getResources();
191 mRadius = (int) res.getDimensionPixelSize(R.dimen.pie_radius_start);
Michael Kolb3daa3512013-04-02 16:03:04 -0700192 mRadiusInc = (int) res.getDimensionPixelSize(R.dimen.pie_radius_increment);
Michael Kolb8872c232013-01-29 10:33:22 -0800193 mCircleSize = mRadius - res.getDimensionPixelSize(R.dimen.focus_radius_offset);
Michael Kolb8872c232013-01-29 10:33:22 -0800194 mTouchOffset = (int) res.getDimensionPixelSize(R.dimen.pie_touch_offset);
Michael Kolb8872c232013-01-29 10:33:22 -0800195 mSelectedPaint = new Paint();
196 mSelectedPaint.setColor(Color.argb(255, 51, 181, 229));
197 mSelectedPaint.setAntiAlias(true);
198 mSubPaint = new Paint();
199 mSubPaint.setAntiAlias(true);
200 mSubPaint.setColor(Color.argb(200, 250, 230, 128));
201 mFocusPaint = new Paint();
202 mFocusPaint.setAntiAlias(true);
203 mFocusPaint.setColor(Color.WHITE);
204 mFocusPaint.setStyle(Paint.Style.STROKE);
205 mSuccessColor = Color.GREEN;
206 mFailColor = Color.RED;
207 mCircle = new RectF();
208 mDial = new RectF();
209 mPoint1 = new Point();
210 mPoint2 = new Point();
211 mInnerOffset = res.getDimensionPixelSize(R.dimen.focus_inner_offset);
212 mOuterStroke = res.getDimensionPixelSize(R.dimen.focus_outer_stroke);
213 mInnerStroke = res.getDimensionPixelSize(R.dimen.focus_inner_stroke);
214 mState = STATE_IDLE;
215 mBlockFocus = false;
216 mTouchSlopSquared = ViewConfiguration.get(ctx).getScaledTouchSlop();
217 mTouchSlopSquared = mTouchSlopSquared * mTouchSlopSquared;
218 mDown = new Point();
Michael Kolb3bc96b22013-03-12 10:24:42 -0700219 mMenuArcPaint = new Paint();
220 mMenuArcPaint.setAntiAlias(true);
221 mMenuArcPaint.setColor(Color.argb(140, 255, 255, 255));
222 mMenuArcPaint.setStrokeWidth(10);
223 mMenuArcPaint.setStyle(Paint.Style.STROKE);
Michael Kolb3daa3512013-04-02 16:03:04 -0700224 mSliceRadius = res.getDimensionPixelSize(R.dimen.pie_item_radius);
Michael Kolb3bc96b22013-03-12 10:24:42 -0700225 mArcRadius = res.getDimensionPixelSize(R.dimen.pie_arc_radius);
226 mArcOffset = res.getDimensionPixelSize(R.dimen.pie_arc_offset);
Michael Kolb10f4ba02013-04-10 08:50:51 -0700227 mLabel = new TextDrawable(res);
Michael Kolbf4651102013-04-19 16:21:24 -0700228 mLabel.setDropShadow(true);
Michael Kolb0f4a5ae2013-04-10 14:16:19 -0700229 mDeadZone = res.getDimensionPixelSize(R.dimen.pie_deadzone_width);
230 mAngleZone = res.getDimensionPixelSize(R.dimen.pie_anglezone_width);
Michael Kolb3bc96b22013-03-12 10:24:42 -0700231 }
232
233 private PieItem getRoot() {
234 return mOpen.get(0);
Michael Kolb8872c232013-01-29 10:33:22 -0800235 }
236
237 public boolean showsItems() {
238 return mTapMode;
239 }
240
241 public void addItem(PieItem item) {
242 // add the item to the pie itself
Michael Kolb3bc96b22013-03-12 10:24:42 -0700243 getRoot().addItem(item);
Michael Kolb8872c232013-01-29 10:33:22 -0800244 }
245
246 public void clearItems() {
Michael Kolb3bc96b22013-03-12 10:24:42 -0700247 getRoot().clearItems();
Michael Kolb8872c232013-01-29 10:33:22 -0800248 }
249
250 public void showInCenter() {
251 if ((mState == STATE_PIE) && isVisible()) {
252 mTapMode = false;
253 show(false);
254 } else {
255 if (mState != STATE_IDLE) {
256 cancelFocus();
257 }
258 mState = STATE_PIE;
Michael Kolb3bc96b22013-03-12 10:24:42 -0700259 resetPieCenter();
260 setCenter(mPieCenterX, mPieCenterY);
Michael Kolb8872c232013-01-29 10:33:22 -0800261 mTapMode = true;
262 show(true);
263 }
264 }
265
266 public void hide() {
267 show(false);
268 }
269
270 /**
271 * guaranteed has center set
272 * @param show
273 */
274 private void show(boolean show) {
275 if (show) {
Michael Kolb0233bad2013-04-24 14:49:46 -0700276 if (mXFade != null) {
277 mXFade.cancel();
278 }
Michael Kolb8872c232013-01-29 10:33:22 -0800279 mState = STATE_PIE;
280 // ensure clean state
281 mCurrentItem = null;
Michael Kolb3bc96b22013-03-12 10:24:42 -0700282 PieItem root = getRoot();
283 for (PieItem openItem : mOpen) {
284 if (openItem.hasItems()) {
285 for (PieItem item : openItem.getItems()) {
286 item.setSelected(false);
287 }
288 }
Michael Kolb8872c232013-01-29 10:33:22 -0800289 }
Michael Kolb10f4ba02013-04-10 08:50:51 -0700290 mLabel.setText("");
Michael Kolb3bc96b22013-03-12 10:24:42 -0700291 mOpen.clear();
292 mOpen.add(root);
Michael Kolb8872c232013-01-29 10:33:22 -0800293 layoutPie();
294 fadeIn();
295 } else {
296 mState = STATE_IDLE;
297 mTapMode = false;
298 if (mXFade != null) {
299 mXFade.cancel();
300 }
Michael Kolb9e012f82013-04-26 08:53:49 -0700301 if (mLabel != null) {
302 mLabel.setText("");
303 }
Michael Kolb8872c232013-01-29 10:33:22 -0800304 }
305 setVisible(show);
306 mHandler.sendEmptyMessage(show ? MSG_OPEN : MSG_CLOSE);
307 }
308
Doris Liufb57df12013-05-14 14:37:46 -0700309 public boolean isOpen() {
310 return mState == STATE_PIE && isVisible();
311 }
312
Michael Kolb8872c232013-01-29 10:33:22 -0800313 private void fadeIn() {
Doris Liu1b9362e2013-07-11 16:32:13 -0700314 mFadeIn = new ValueAnimator();
315 mFadeIn.setFloatValues(0f, 1f);
Michael Kolb8872c232013-01-29 10:33:22 -0800316 mFadeIn.setDuration(PIE_FADE_IN_DURATION);
Doris Liu1b9362e2013-07-11 16:32:13 -0700317 // linear interpolation
318 mFadeIn.setInterpolator(null);
319 mFadeIn.addListener(new AnimatorListener() {
Michael Kolb8872c232013-01-29 10:33:22 -0800320 @Override
Doris Liu1b9362e2013-07-11 16:32:13 -0700321 public void onAnimationStart(Animator animation) {
Michael Kolb8872c232013-01-29 10:33:22 -0800322 }
323
324 @Override
Doris Liu1b9362e2013-07-11 16:32:13 -0700325 public void onAnimationEnd(Animator animation) {
Michael Kolb8872c232013-01-29 10:33:22 -0800326 mFadeIn = null;
327 }
328
329 @Override
Doris Liu1b9362e2013-07-11 16:32:13 -0700330 public void onAnimationRepeat(Animator animation) {
331 }
332
333 @Override
334 public void onAnimationCancel(Animator arg0) {
Michael Kolb8872c232013-01-29 10:33:22 -0800335 }
336 });
Doris Liu1b9362e2013-07-11 16:32:13 -0700337 mFadeIn.start();
Michael Kolb8872c232013-01-29 10:33:22 -0800338 }
339
340 public void setCenter(int x, int y) {
Michael Kolb3bc96b22013-03-12 10:24:42 -0700341 mPieCenterX = x;
342 mPieCenterY = y;
Michael Kolb0f4a5ae2013-04-10 14:16:19 -0700343 mSliceCenterY = y + mSliceRadius - mArcOffset;
Michael Kolb3daa3512013-04-02 16:03:04 -0700344 mArcCenterY = y - mArcOffset + mArcRadius;
Michael Kolb3bc96b22013-03-12 10:24:42 -0700345 }
346
347 @Override
348 public void layout(int l, int t, int r, int b) {
349 super.layout(l, t, r, b);
350 mCenterX = (r - l) / 2;
351 mCenterY = (b - t) / 2;
352
353 mFocusX = mCenterX;
354 mFocusY = mCenterY;
355 resetPieCenter();
356 setCircle(mFocusX, mFocusY);
357 if (isVisible() && mState == STATE_PIE) {
358 setCenter(mPieCenterX, mPieCenterY);
359 layoutPie();
360 }
361 }
362
363 private void resetPieCenter() {
364 mPieCenterX = mCenterX;
Michael Kolbf4651102013-04-19 16:21:24 -0700365 mPieCenterY = (int) (getHeight() - 2.5f * mDeadZone);
Michael Kolb8872c232013-01-29 10:33:22 -0800366 }
367
368 private void layoutPie() {
Michael Kolb0f4a5ae2013-04-10 14:16:19 -0700369 mCenterAngle = getCenterAngle();
Michael Kolb3daa3512013-04-02 16:03:04 -0700370 layoutItems(0, getRoot().getItems());
Michael Kolb60e938e2013-05-14 14:28:14 -0700371 layoutLabel(getLevel());
Michael Kolb10f4ba02013-04-10 08:50:51 -0700372 }
373
374 private void layoutLabel(int level) {
Michael Kolb0f4a5ae2013-04-10 14:16:19 -0700375 int x = mPieCenterX - (int) (FloatMath.sin(mCenterAngle - CENTER)
376 * (mArcRadius + (level + 2) * mRadiusInc));
Michael Kolb10f4ba02013-04-10 08:50:51 -0700377 int y = mArcCenterY - mArcRadius - (level + 2) * mRadiusInc;
378 int w = mLabel.getIntrinsicWidth();
379 int h = mLabel.getIntrinsicHeight();
380 mLabel.setBounds(x - w/2, y - h/2, x + w/2, y + h/2);
Michael Kolb8872c232013-01-29 10:33:22 -0800381 }
382
Michael Kolb3daa3512013-04-02 16:03:04 -0700383 private void layoutItems(int level, List<PieItem> items) {
384 int extend = 1;
385 Path path = makeSlice(getDegrees(0) + extend, getDegrees(SWEEP_ARC) - extend,
386 mArcRadius, mArcRadius + mRadiusInc + mRadiusInc / 4,
387 mPieCenterX, mArcCenterY - level * mRadiusInc);
Michael Kolbb7c49992013-04-30 09:23:23 -0700388 final int count = items.size();
389 int pos = 0;
Michael Kolb8872c232013-01-29 10:33:22 -0800390 for (PieItem item : items) {
391 // shared between items
392 item.setPath(path);
Michael Kolbb7c49992013-04-30 09:23:23 -0700393 float angle = getArcCenter(item, pos, count);
Michael Kolb8872c232013-01-29 10:33:22 -0800394 int w = item.getIntrinsicWidth();
395 int h = item.getIntrinsicHeight();
396 // move views to outer border
Michael Kolb3daa3512013-04-02 16:03:04 -0700397 int r = mArcRadius + mRadiusInc * 2 / 3;
Michael Kolb8872c232013-01-29 10:33:22 -0800398 int x = (int) (r * Math.cos(angle));
Michael Kolb3daa3512013-04-02 16:03:04 -0700399 int y = mArcCenterY - (level * mRadiusInc) - (int) (r * Math.sin(angle)) - h / 2;
400 x = mPieCenterX + x - w / 2;
Michael Kolb8872c232013-01-29 10:33:22 -0800401 item.setBounds(x, y, x + w, y + h);
Michael Kolb3daa3512013-04-02 16:03:04 -0700402 item.setLevel(level);
Michael Kolb8872c232013-01-29 10:33:22 -0800403 if (item.hasItems()) {
Michael Kolb3daa3512013-04-02 16:03:04 -0700404 layoutItems(level + 1, item.getItems());
Michael Kolb8872c232013-01-29 10:33:22 -0800405 }
Michael Kolbb7c49992013-04-30 09:23:23 -0700406 pos++;
Michael Kolb8872c232013-01-29 10:33:22 -0800407 }
408 }
409
Michael Kolb3daa3512013-04-02 16:03:04 -0700410 private Path makeSlice(float start, float end, int inner, int outer, int cx, int cy) {
Michael Kolb8872c232013-01-29 10:33:22 -0800411 RectF bb =
Michael Kolb3daa3512013-04-02 16:03:04 -0700412 new RectF(cx - outer, cy - outer, cx + outer,
413 cy + outer);
Michael Kolb8872c232013-01-29 10:33:22 -0800414 RectF bbi =
Michael Kolb3daa3512013-04-02 16:03:04 -0700415 new RectF(cx - inner, cy - inner, cx + inner,
416 cy + inner);
Michael Kolb8872c232013-01-29 10:33:22 -0800417 Path path = new Path();
418 path.arcTo(bb, start, end - start, true);
419 path.arcTo(bbi, end, start - end);
420 path.close();
421 return path;
422 }
423
Michael Kolbb7c49992013-04-30 09:23:23 -0700424 private float getArcCenter(PieItem item, int pos, int count) {
425 return getCenter(pos, count, SWEEP_ARC);
Michael Kolb3daa3512013-04-02 16:03:04 -0700426 }
427
Michael Kolbb7c49992013-04-30 09:23:23 -0700428 private float getSliceCenter(PieItem item, int pos, int count) {
Michael Kolb0f4a5ae2013-04-10 14:16:19 -0700429 float center = (getCenterAngle() - CENTER) * 0.5f + CENTER;
Michael Kolbb7c49992013-04-30 09:23:23 -0700430 return center + (count - 1) * SWEEP_SLICE / 2f
431 - pos * SWEEP_SLICE;
Michael Kolb3daa3512013-04-02 16:03:04 -0700432 }
433
434 private float getCenter(int pos, int count, float sweep) {
Michael Kolb0f4a5ae2013-04-10 14:16:19 -0700435 return mCenterAngle + (count - 1) * sweep / 2f - pos * sweep;
436 }
437
438 private float getCenterAngle() {
439 float center = CENTER;
440 if (mPieCenterX < mDeadZone + mAngleZone) {
Michael Kolbf4651102013-04-19 16:21:24 -0700441 center = CENTER - (mAngleZone - mPieCenterX + mDeadZone) * RAD24
Michael Kolb0f4a5ae2013-04-10 14:16:19 -0700442 / (float) mAngleZone;
443 } else if (mPieCenterX > getWidth() - mDeadZone - mAngleZone) {
Michael Kolbf4651102013-04-19 16:21:24 -0700444 center = CENTER + (mPieCenterX - (getWidth() - mDeadZone - mAngleZone)) * RAD24
Michael Kolb0f4a5ae2013-04-10 14:16:19 -0700445 / (float) mAngleZone;
446 }
447 return center;
Michael Kolb3daa3512013-04-02 16:03:04 -0700448 }
449
Michael Kolb8872c232013-01-29 10:33:22 -0800450 /**
451 * converts a
452 * @param angle from 0..PI to Android degrees (clockwise starting at 3 o'clock)
453 * @return skia angle
454 */
455 private float getDegrees(double angle) {
456 return (float) (360 - 180 * angle / Math.PI);
457 }
458
Michael Kolbebcf6fe2013-02-27 11:00:31 -0800459 private void startFadeOut(final PieItem item) {
460 if (mFadeIn != null) {
461 mFadeIn.cancel();
Michael Kolb8872c232013-01-29 10:33:22 -0800462 }
Michael Kolbebcf6fe2013-02-27 11:00:31 -0800463 if (mXFade != null) {
464 mXFade.cancel();
465 }
Doris Liu1b9362e2013-07-11 16:32:13 -0700466 mFadeOut = new ValueAnimator();
467 mFadeOut.setFloatValues(1f, 0f);
Michael Kolbebcf6fe2013-02-27 11:00:31 -0800468 mFadeOut.setDuration(PIE_FADE_OUT_DURATION);
Doris Liu1b9362e2013-07-11 16:32:13 -0700469 mFadeOut.addListener(new AnimatorListener() {
Michael Kolbebcf6fe2013-02-27 11:00:31 -0800470 @Override
Doris Liu1b9362e2013-07-11 16:32:13 -0700471 public void onAnimationStart(Animator animator) {
Michael Kolbebcf6fe2013-02-27 11:00:31 -0800472 }
473
474 @Override
Doris Liu1b9362e2013-07-11 16:32:13 -0700475 public void onAnimationEnd(Animator animator) {
Michael Kolbebcf6fe2013-02-27 11:00:31 -0800476 item.performClick();
477 mFadeOut = null;
478 deselect();
479 show(false);
480 mOverlay.setAlpha(1);
481 }
482
483 @Override
Doris Liu1b9362e2013-07-11 16:32:13 -0700484 public void onAnimationRepeat(Animator animator) {
Michael Kolbebcf6fe2013-02-27 11:00:31 -0800485 }
Doris Liu1b9362e2013-07-11 16:32:13 -0700486
487 @Override
488 public void onAnimationCancel(Animator animator) {
489 }
490
Michael Kolbebcf6fe2013-02-27 11:00:31 -0800491 });
Doris Liu1b9362e2013-07-11 16:32:13 -0700492 mFadeOut.start();
Michael Kolb8872c232013-01-29 10:33:22 -0800493 }
494
Michael Kolb3bc96b22013-03-12 10:24:42 -0700495 // root does not count
496 private boolean hasOpenItem() {
497 return mOpen.size() > 1;
498 }
499
500 // pop an item of the open item stack
501 private PieItem closeOpenItem() {
502 PieItem item = getOpenItem();
503 mOpen.remove(mOpen.size() -1);
504 return item;
505 }
506
507 private PieItem getOpenItem() {
508 return mOpen.get(mOpen.size() - 1);
509 }
510
511 // return the children either the root or parent of the current open item
512 private PieItem getParent() {
513 return mOpen.get(Math.max(0, mOpen.size() - 2));
514 }
515
516 private int getLevel() {
517 return mOpen.size() - 1;
518 }
519
Michael Kolb8872c232013-01-29 10:33:22 -0800520 @Override
521 public void onDraw(Canvas canvas) {
522 float alpha = 1;
523 if (mXFade != null) {
Doris Liu1b9362e2013-07-11 16:32:13 -0700524 alpha = (Float) mXFade.getAnimatedValue();
Michael Kolb8872c232013-01-29 10:33:22 -0800525 } else if (mFadeIn != null) {
Doris Liu1b9362e2013-07-11 16:32:13 -0700526 alpha = (Float) mFadeIn.getAnimatedValue();
Michael Kolbebcf6fe2013-02-27 11:00:31 -0800527 } else if (mFadeOut != null) {
Doris Liu1b9362e2013-07-11 16:32:13 -0700528 alpha = (Float) mFadeOut.getAnimatedValue();
Michael Kolb8872c232013-01-29 10:33:22 -0800529 }
530 int state = canvas.save();
531 if (mFadeIn != null) {
532 float sf = 0.9f + alpha * 0.1f;
Michael Kolb3bc96b22013-03-12 10:24:42 -0700533 canvas.scale(sf, sf, mPieCenterX, mPieCenterY);
Michael Kolb8872c232013-01-29 10:33:22 -0800534 }
Michael Kolb3bc96b22013-03-12 10:24:42 -0700535 if (mState != STATE_PIE) {
536 drawFocus(canvas);
537 }
Michael Kolb8872c232013-01-29 10:33:22 -0800538 if (mState == STATE_FINISHING) {
539 canvas.restoreToCount(state);
540 return;
541 }
Michael Kolb9e012f82013-04-26 08:53:49 -0700542 if (mState != STATE_PIE) return;
Michael Kolb3bc96b22013-03-12 10:24:42 -0700543 if (!hasOpenItem() || (mXFade != null)) {
Michael Kolb8872c232013-01-29 10:33:22 -0800544 // draw base menu
Michael Kolb3daa3512013-04-02 16:03:04 -0700545 drawArc(canvas, getLevel(), getParent());
Michael Kolbb7c49992013-04-30 09:23:23 -0700546 List<PieItem> items = getParent().getItems();
547 final int count = items.size();
548 int pos = 0;
Michael Kolb3bc96b22013-03-12 10:24:42 -0700549 for (PieItem item : getParent().getItems()) {
Michael Kolbb7c49992013-04-30 09:23:23 -0700550 drawItem(Math.max(0, mOpen.size() - 2), pos, count, canvas, item, alpha);
551 pos++;
Michael Kolb8872c232013-01-29 10:33:22 -0800552 }
Michael Kolb10f4ba02013-04-10 08:50:51 -0700553 mLabel.draw(canvas);
Michael Kolb8872c232013-01-29 10:33:22 -0800554 }
Michael Kolb3bc96b22013-03-12 10:24:42 -0700555 if (hasOpenItem()) {
556 int level = getLevel();
Michael Kolb3daa3512013-04-02 16:03:04 -0700557 drawArc(canvas, level, getOpenItem());
Michael Kolbb7c49992013-04-30 09:23:23 -0700558 List<PieItem> items = getOpenItem().getItems();
559 final int count = items.size();
560 int pos = 0;
561 for (PieItem inner : items) {
Michael Kolbebcf6fe2013-02-27 11:00:31 -0800562 if (mFadeOut != null) {
Michael Kolbb7c49992013-04-30 09:23:23 -0700563 drawItem(level, pos, count, canvas, inner, alpha);
Michael Kolbebcf6fe2013-02-27 11:00:31 -0800564 } else {
Michael Kolbb7c49992013-04-30 09:23:23 -0700565 drawItem(level, pos, count, canvas, inner, (mXFade != null) ? (1 - 0.5f * alpha) : 1);
Michael Kolbebcf6fe2013-02-27 11:00:31 -0800566 }
Michael Kolbb7c49992013-04-30 09:23:23 -0700567 pos++;
Michael Kolb8872c232013-01-29 10:33:22 -0800568 }
Michael Kolb10f4ba02013-04-10 08:50:51 -0700569 mLabel.draw(canvas);
Michael Kolb8872c232013-01-29 10:33:22 -0800570 }
571 canvas.restoreToCount(state);
572 }
573
Michael Kolb3daa3512013-04-02 16:03:04 -0700574 private void drawArc(Canvas canvas, int level, PieItem item) {
Michael Kolb3bc96b22013-03-12 10:24:42 -0700575 // arc
576 if (mState == STATE_PIE) {
Michael Kolbb7c49992013-04-30 09:23:23 -0700577 final int count = item.getItems().size();
578 float start = mCenterAngle + (count * SWEEP_ARC / 2f);
579 float end = mCenterAngle - (count * SWEEP_ARC / 2f);
Michael Kolb3daa3512013-04-02 16:03:04 -0700580 int cy = mArcCenterY - level * mRadiusInc;
581 canvas.drawArc(new RectF(mPieCenterX - mArcRadius, cy - mArcRadius,
582 mPieCenterX + mArcRadius, cy + mArcRadius),
583 getDegrees(end), getDegrees(start) - getDegrees(end), false, mMenuArcPaint);
Michael Kolb3bc96b22013-03-12 10:24:42 -0700584 }
585 }
Michael Kolb3daa3512013-04-02 16:03:04 -0700586
Michael Kolbb7c49992013-04-30 09:23:23 -0700587 private void drawItem(int level, int pos, int count, Canvas canvas, PieItem item, float alpha) {
Michael Kolb8872c232013-01-29 10:33:22 -0800588 if (mState == STATE_PIE) {
589 if (item.getPath() != null) {
Michael Kolb3daa3512013-04-02 16:03:04 -0700590 int y = mArcCenterY - level * mRadiusInc;
Michael Kolb8872c232013-01-29 10:33:22 -0800591 if (item.isSelected()) {
592 Paint p = mSelectedPaint;
593 int state = canvas.save();
Michael Kolbad2a7452013-05-07 13:53:19 -0700594 float angle = 0;
595 if (mSlice != null) {
Doris Liu1b9362e2013-07-11 16:32:13 -0700596 angle = (Float) mSlice.getAnimatedValue();
Michael Kolbad2a7452013-05-07 13:53:19 -0700597 } else {
598 angle = getArcCenter(item, pos, count) - SWEEP_ARC / 2f;
599 }
Michael Kolb3daa3512013-04-02 16:03:04 -0700600 angle = getDegrees(angle);
601 canvas.rotate(angle, mPieCenterX, y);
Michael Kolbebcf6fe2013-02-27 11:00:31 -0800602 if (mFadeOut != null) {
603 p.setAlpha((int)(255 * alpha));
604 }
Michael Kolb8872c232013-01-29 10:33:22 -0800605 canvas.drawPath(item.getPath(), p);
Michael Kolbebcf6fe2013-02-27 11:00:31 -0800606 if (mFadeOut != null) {
607 p.setAlpha(255);
608 }
Michael Kolb8872c232013-01-29 10:33:22 -0800609 canvas.restoreToCount(state);
610 }
Michael Kolbebcf6fe2013-02-27 11:00:31 -0800611 if (mFadeOut == null) {
612 alpha = alpha * (item.isEnabled() ? 1 : 0.3f);
613 // draw the item view
614 item.setAlpha(alpha);
615 }
Michael Kolb8872c232013-01-29 10:33:22 -0800616 item.draw(canvas);
617 }
618 }
619 }
620
621 @Override
622 public boolean onTouchEvent(MotionEvent evt) {
623 float x = evt.getX();
624 float y = evt.getY();
625 int action = evt.getActionMasked();
Michael Kolb3daa3512013-04-02 16:03:04 -0700626 getPolar(x, y, !mTapMode, mPolar);
Michael Kolb8872c232013-01-29 10:33:22 -0800627 if (MotionEvent.ACTION_DOWN == action) {
Michael Kolb0f4a5ae2013-04-10 14:16:19 -0700628 if ((x < mDeadZone) || (x > getWidth() - mDeadZone)) {
629 return false;
630 }
Michael Kolb8872c232013-01-29 10:33:22 -0800631 mDown.x = (int) evt.getX();
632 mDown.y = (int) evt.getY();
633 mOpening = false;
634 if (mTapMode) {
Michael Kolb3daa3512013-04-02 16:03:04 -0700635 PieItem item = findItem(mPolar);
Michael Kolb8872c232013-01-29 10:33:22 -0800636 if ((item != null) && (mCurrentItem != item)) {
637 mState = STATE_PIE;
638 onEnter(item);
639 }
640 } else {
641 setCenter((int) x, (int) y);
642 show(true);
643 }
644 return true;
645 } else if (MotionEvent.ACTION_UP == action) {
646 if (isVisible()) {
647 PieItem item = mCurrentItem;
648 if (mTapMode) {
Michael Kolb3daa3512013-04-02 16:03:04 -0700649 item = findItem(mPolar);
Michael Kolb3bc96b22013-03-12 10:24:42 -0700650 if (mOpening) {
Michael Kolb8872c232013-01-29 10:33:22 -0800651 mOpening = false;
652 return true;
653 }
654 }
655 if (item == null) {
656 mTapMode = false;
657 show(false);
Michael Kolb3bc96b22013-03-12 10:24:42 -0700658 } else if (!mOpening && !item.hasItems()) {
659 startFadeOut(item);
660 mTapMode = false;
661 } else {
662 mTapMode = true;
Michael Kolb8872c232013-01-29 10:33:22 -0800663 }
664 return true;
665 }
666 } else if (MotionEvent.ACTION_CANCEL == action) {
667 if (isVisible() || mTapMode) {
668 show(false);
669 }
670 deselect();
Michael Kolb3bc96b22013-03-12 10:24:42 -0700671 mHandler.removeMessages(MSG_OPENSUBMENU);
Michael Kolb8872c232013-01-29 10:33:22 -0800672 return false;
673 } else if (MotionEvent.ACTION_MOVE == action) {
Michael Kolb3daa3512013-04-02 16:03:04 -0700674 if (pulledToCenter(mPolar)) {
Michael Kolb3bc96b22013-03-12 10:24:42 -0700675 mHandler.removeMessages(MSG_OPENSUBMENU);
676 if (hasOpenItem()) {
677 if (mCurrentItem != null) {
678 mCurrentItem.setSelected(false);
679 }
680 closeOpenItem();
681 mCurrentItem = null;
Michael Kolb8872c232013-01-29 10:33:22 -0800682 } else {
683 deselect();
684 }
Michael Kolb10f4ba02013-04-10 08:50:51 -0700685 mLabel.setText("");
Michael Kolb8872c232013-01-29 10:33:22 -0800686 return false;
687 }
Michael Kolb3daa3512013-04-02 16:03:04 -0700688 PieItem item = findItem(mPolar);
Michael Kolb8872c232013-01-29 10:33:22 -0800689 boolean moved = hasMoved(evt);
690 if ((item != null) && (mCurrentItem != item) && (!mOpening || moved)) {
Michael Kolb3bc96b22013-03-12 10:24:42 -0700691 mHandler.removeMessages(MSG_OPENSUBMENU);
Michael Kolb8872c232013-01-29 10:33:22 -0800692 // only select if we didn't just open or have moved past slop
Michael Kolb8872c232013-01-29 10:33:22 -0800693 if (moved) {
694 // switch back to swipe mode
695 mTapMode = false;
696 }
Michael Kolb3bc96b22013-03-12 10:24:42 -0700697 onEnterSelect(item);
698 mHandler.sendEmptyMessageDelayed(MSG_OPENSUBMENU, PIE_OPEN_SUB_DELAY);
Michael Kolb8872c232013-01-29 10:33:22 -0800699 }
700 }
701 return false;
702 }
703
Sascha Haeberlingb7b73ec2013-11-12 15:16:08 -0800704 @Override
705 public boolean isVisible() {
Erin Dahlgren5ce035e2013-12-10 17:16:17 -0800706 return super.isVisible();
Sascha Haeberlingb7b73ec2013-11-12 15:16:08 -0800707 }
708
Michael Kolb3bc96b22013-03-12 10:24:42 -0700709 private boolean pulledToCenter(PointF polarCoords) {
Michael Kolb3daa3512013-04-02 16:03:04 -0700710 return polarCoords.y < mArcRadius - mRadiusInc;
Michael Kolb3bc96b22013-03-12 10:24:42 -0700711 }
712
Michael Kolbb7c49992013-04-30 09:23:23 -0700713 private boolean inside(PointF polar, PieItem item, int pos, int count) {
714 float start = getSliceCenter(item, pos, count) - SWEEP_SLICE / 2f;
Michael Kolb3daa3512013-04-02 16:03:04 -0700715 boolean res = (mArcRadius < polar.y)
716 && (start < polar.x)
717 && (start + SWEEP_SLICE > polar.x)
718 && (!mTapMode || (mArcRadius + mRadiusInc > polar.y));
719 return res;
720 }
721
722 private void getPolar(float x, float y, boolean useOffset, PointF res) {
Michael Kolb3bc96b22013-03-12 10:24:42 -0700723 // get angle and radius from x/y
724 res.x = (float) Math.PI / 2;
Michael Kolb3daa3512013-04-02 16:03:04 -0700725 x = x - mPieCenterX;
726 float y1 = mSliceCenterY - getLevel() * mRadiusInc - y;
727 float y2 = mArcCenterY - getLevel() * mRadiusInc - y;
728 res.y = (float) Math.sqrt(x * x + y2 * y2);
Michael Kolb3bc96b22013-03-12 10:24:42 -0700729 if (x != 0) {
Michael Kolb3daa3512013-04-02 16:03:04 -0700730 res.x = (float) Math.atan2(y1, x);
Michael Kolb3bc96b22013-03-12 10:24:42 -0700731 if (res.x < 0) {
732 res.x = (float) (2 * Math.PI + res.x);
733 }
734 }
735 res.y = res.y + (useOffset ? mTouchOffset : 0);
Michael Kolb3bc96b22013-03-12 10:24:42 -0700736 }
737
Michael Kolb8872c232013-01-29 10:33:22 -0800738 private boolean hasMoved(MotionEvent e) {
739 return mTouchSlopSquared < (e.getX() - mDown.x) * (e.getX() - mDown.x)
740 + (e.getY() - mDown.y) * (e.getY() - mDown.y);
741 }
742
Michael Kolb3bc96b22013-03-12 10:24:42 -0700743 private void onEnterSelect(PieItem item) {
744 if (mCurrentItem != null) {
745 mCurrentItem.setSelected(false);
746 }
747 if (item != null && item.isEnabled()) {
Michael Kolbad2a7452013-05-07 13:53:19 -0700748 moveSelection(mCurrentItem, item);
Michael Kolb3bc96b22013-03-12 10:24:42 -0700749 item.setSelected(true);
750 mCurrentItem = item;
Michael Kolb10f4ba02013-04-10 08:50:51 -0700751 mLabel.setText(mCurrentItem.getLabel());
752 layoutLabel(getLevel());
Michael Kolb3bc96b22013-03-12 10:24:42 -0700753 } else {
754 mCurrentItem = null;
755 }
756 }
757
758 private void onEnterOpen() {
Michael Kolbdc7d1ac2013-04-12 08:45:25 -0700759 if ((mCurrentItem != null) && (mCurrentItem != getOpenItem()) && mCurrentItem.hasItems()) {
Michael Kolb3bc96b22013-03-12 10:24:42 -0700760 openCurrentItem();
761 }
762 }
763
Michael Kolb8872c232013-01-29 10:33:22 -0800764 /**
765 * enter a slice for a view
766 * updates model only
767 * @param item
768 */
769 private void onEnter(PieItem item) {
770 if (mCurrentItem != null) {
771 mCurrentItem.setSelected(false);
772 }
773 if (item != null && item.isEnabled()) {
774 item.setSelected(true);
775 mCurrentItem = item;
Michael Kolb712cc2f2013-05-06 08:49:10 -0700776 mLabel.setText(mCurrentItem.getLabel());
Michael Kolb3bc96b22013-03-12 10:24:42 -0700777 if ((mCurrentItem != getOpenItem()) && mCurrentItem.hasItems()) {
Michael Kolb8872c232013-01-29 10:33:22 -0800778 openCurrentItem();
Michael Kolb10f4ba02013-04-10 08:50:51 -0700779 layoutLabel(getLevel());
Michael Kolb8872c232013-01-29 10:33:22 -0800780 }
781 } else {
782 mCurrentItem = null;
783 }
784 }
785
786 private void deselect() {
787 if (mCurrentItem != null) {
788 mCurrentItem.setSelected(false);
789 }
Michael Kolb3bc96b22013-03-12 10:24:42 -0700790 if (hasOpenItem()) {
791 PieItem item = closeOpenItem();
792 onEnter(item);
793 } else {
794 mCurrentItem = null;
Michael Kolb8872c232013-01-29 10:33:22 -0800795 }
Michael Kolb8872c232013-01-29 10:33:22 -0800796 }
797
Michael Kolbad2a7452013-05-07 13:53:19 -0700798 private int getItemPos(PieItem target) {
799 List<PieItem> items = getOpenItem().getItems();
800 return items.indexOf(target);
801 }
802
803 private int getCurrentCount() {
804 return getOpenItem().getItems().size();
805 }
806
807 private void moveSelection(PieItem from, PieItem to) {
808 final int count = getCurrentCount();
809 final int fromPos = getItemPos(from);
810 final int toPos = getItemPos(to);
811 if (fromPos != -1 && toPos != -1) {
812 float startAngle = getArcCenter(from, getItemPos(from), count)
813 - SWEEP_ARC / 2f;
814 float endAngle = getArcCenter(to, getItemPos(to), count)
815 - SWEEP_ARC / 2f;
Doris Liu1b9362e2013-07-11 16:32:13 -0700816 mSlice = new ValueAnimator();
817 mSlice.setFloatValues(startAngle, endAngle);
818 // linear interpolater
819 mSlice.setInterpolator(null);
Michael Kolbad2a7452013-05-07 13:53:19 -0700820 mSlice.setDuration(PIE_SLICE_DURATION);
Doris Liu1b9362e2013-07-11 16:32:13 -0700821 mSlice.addListener(new AnimatorListener() {
Michael Kolbad2a7452013-05-07 13:53:19 -0700822 @Override
Doris Liu1b9362e2013-07-11 16:32:13 -0700823 public void onAnimationEnd(Animator arg0) {
Michael Kolbad2a7452013-05-07 13:53:19 -0700824 mSlice = null;
825 }
826
827 @Override
Doris Liu1b9362e2013-07-11 16:32:13 -0700828 public void onAnimationRepeat(Animator arg0) {
Michael Kolbad2a7452013-05-07 13:53:19 -0700829 }
830
831 @Override
Doris Liu1b9362e2013-07-11 16:32:13 -0700832 public void onAnimationStart(Animator arg0) {
833 }
834
835 @Override
836 public void onAnimationCancel(Animator arg0) {
Michael Kolbad2a7452013-05-07 13:53:19 -0700837 }
838 });
Doris Liu1b9362e2013-07-11 16:32:13 -0700839 mSlice.start();
Michael Kolbad2a7452013-05-07 13:53:19 -0700840 }
841 }
842
Michael Kolb8872c232013-01-29 10:33:22 -0800843 private void openCurrentItem() {
844 if ((mCurrentItem != null) && mCurrentItem.hasItems()) {
Michael Kolb3bc96b22013-03-12 10:24:42 -0700845 mOpen.add(mCurrentItem);
Michael Kolbf4651102013-04-19 16:21:24 -0700846 layoutLabel(getLevel());
Michael Kolb8872c232013-01-29 10:33:22 -0800847 mOpening = true;
Michael Kolbebcf6fe2013-02-27 11:00:31 -0800848 if (mFadeIn != null) {
849 mFadeIn.cancel();
850 }
Doris Liu1b9362e2013-07-11 16:32:13 -0700851 mXFade = new ValueAnimator();
852 mXFade.setFloatValues(1f, 0f);
Michael Kolb8872c232013-01-29 10:33:22 -0800853 mXFade.setDuration(PIE_XFADE_DURATION);
Doris Liu1b9362e2013-07-11 16:32:13 -0700854 // Linear interpolation
855 mXFade.setInterpolator(null);
Michael Kolb3bc96b22013-03-12 10:24:42 -0700856 final PieItem ci = mCurrentItem;
Doris Liu1b9362e2013-07-11 16:32:13 -0700857 mXFade.addListener(new AnimatorListener() {
Michael Kolb8872c232013-01-29 10:33:22 -0800858 @Override
Doris Liu1b9362e2013-07-11 16:32:13 -0700859 public void onAnimationStart(Animator animation) {
Michael Kolb8872c232013-01-29 10:33:22 -0800860 }
861
862 @Override
Doris Liu1b9362e2013-07-11 16:32:13 -0700863 public void onAnimationEnd(Animator animation) {
Michael Kolb8872c232013-01-29 10:33:22 -0800864 mXFade = null;
Michael Kolb3bc96b22013-03-12 10:24:42 -0700865 ci.setSelected(false);
866 mOpening = false;
Michael Kolb8872c232013-01-29 10:33:22 -0800867 }
868
869 @Override
Doris Liu1b9362e2013-07-11 16:32:13 -0700870 public void onAnimationRepeat(Animator animation) {
871 }
872
873 @Override
874 public void onAnimationCancel(Animator arg0) {
Michael Kolb8872c232013-01-29 10:33:22 -0800875 }
876 });
Doris Liu1b9362e2013-07-11 16:32:13 -0700877 mXFade.start();
Michael Kolb8872c232013-01-29 10:33:22 -0800878 }
879 }
880
Michael Kolb8872c232013-01-29 10:33:22 -0800881 /**
882 * @param polar x: angle, y: dist
883 * @return the item at angle/dist or null
884 */
885 private PieItem findItem(PointF polar) {
886 // find the matching item:
Michael Kolb3bc96b22013-03-12 10:24:42 -0700887 List<PieItem> items = getOpenItem().getItems();
Michael Kolbb7c49992013-04-30 09:23:23 -0700888 final int count = items.size();
889 int pos = 0;
Michael Kolb8872c232013-01-29 10:33:22 -0800890 for (PieItem item : items) {
Michael Kolbb7c49992013-04-30 09:23:23 -0700891 if (inside(polar, item, pos, count)) {
Michael Kolb8872c232013-01-29 10:33:22 -0800892 return item;
893 }
Michael Kolbb7c49992013-04-30 09:23:23 -0700894 pos++;
Michael Kolb8872c232013-01-29 10:33:22 -0800895 }
896 return null;
897 }
898
Michael Kolb8872c232013-01-29 10:33:22 -0800899
900 @Override
901 public boolean handlesTouch() {
902 return true;
903 }
904
905 // focus specific code
906
907 public void setBlockFocus(boolean blocked) {
908 mBlockFocus = blocked;
909 if (blocked) {
910 clear();
911 }
912 }
913
914 public void setFocus(int x, int y) {
Igor Murashkind9b229b2013-10-15 13:43:31 -0700915 mOverlay.removeCallbacks(mDisappear);
Michael Kolb8872c232013-01-29 10:33:22 -0800916 mFocusX = x;
917 mFocusY = y;
918 setCircle(mFocusX, mFocusY);
919 }
920
Michael Kolb8872c232013-01-29 10:33:22 -0800921 public int getSize() {
922 return 2 * mCircleSize;
923 }
924
925 private int getRandomRange() {
926 return (int)(-60 + 120 * Math.random());
927 }
928
Michael Kolb8872c232013-01-29 10:33:22 -0800929 private void setCircle(int cx, int cy) {
930 mCircle.set(cx - mCircleSize, cy - mCircleSize,
931 cx + mCircleSize, cy + mCircleSize);
932 mDial.set(cx - mCircleSize + mInnerOffset, cy - mCircleSize + mInnerOffset,
933 cx + mCircleSize - mInnerOffset, cy + mCircleSize - mInnerOffset);
934 }
935
936 public void drawFocus(Canvas canvas) {
937 if (mBlockFocus) return;
938 mFocusPaint.setStrokeWidth(mOuterStroke);
939 canvas.drawCircle((float) mFocusX, (float) mFocusY, (float) mCircleSize, mFocusPaint);
940 if (mState == STATE_PIE) return;
941 int color = mFocusPaint.getColor();
942 if (mState == STATE_FINISHING) {
943 mFocusPaint.setColor(mFocused ? mSuccessColor : mFailColor);
944 }
945 mFocusPaint.setStrokeWidth(mInnerStroke);
946 drawLine(canvas, mDialAngle, mFocusPaint);
947 drawLine(canvas, mDialAngle + 45, mFocusPaint);
948 drawLine(canvas, mDialAngle + 180, mFocusPaint);
949 drawLine(canvas, mDialAngle + 225, mFocusPaint);
950 canvas.save();
951 // rotate the arc instead of its offset to better use framework's shape caching
952 canvas.rotate(mDialAngle, mFocusX, mFocusY);
953 canvas.drawArc(mDial, 0, 45, false, mFocusPaint);
954 canvas.drawArc(mDial, 180, 45, false, mFocusPaint);
955 canvas.restore();
956 mFocusPaint.setColor(color);
957 }
958
959 private void drawLine(Canvas canvas, int angle, Paint p) {
960 convertCart(angle, mCircleSize - mInnerOffset, mPoint1);
961 convertCart(angle, mCircleSize - mInnerOffset + mInnerOffset / 3, mPoint2);
962 canvas.drawLine(mPoint1.x + mFocusX, mPoint1.y + mFocusY,
963 mPoint2.x + mFocusX, mPoint2.y + mFocusY, p);
964 }
965
966 private static void convertCart(int angle, int radius, Point out) {
967 double a = 2 * Math.PI * (angle % 360) / 360;
968 out.x = (int) (radius * Math.cos(a) + 0.5);
969 out.y = (int) (radius * Math.sin(a) + 0.5);
970 }
971
972 @Override
973 public void showStart() {
974 if (mState == STATE_PIE) return;
975 cancelFocus();
976 mStartAnimationAngle = 67;
977 int range = getRandomRange();
978 startAnimation(SCALING_UP_TIME,
979 false, mStartAnimationAngle, mStartAnimationAngle + range);
980 mState = STATE_FOCUSING;
981 }
982
983 @Override
984 public void showSuccess(boolean timeout) {
985 if (mState == STATE_FOCUSING) {
986 startAnimation(SCALING_DOWN_TIME,
987 timeout, mStartAnimationAngle);
988 mState = STATE_FINISHING;
989 mFocused = true;
990 }
991 }
992
993 @Override
994 public void showFail(boolean timeout) {
995 if (mState == STATE_FOCUSING) {
996 startAnimation(SCALING_DOWN_TIME,
997 timeout, mStartAnimationAngle);
998 mState = STATE_FINISHING;
999 mFocused = false;
1000 }
1001 }
1002
1003 private void cancelFocus() {
1004 mFocusCancelled = true;
1005 mOverlay.removeCallbacks(mDisappear);
Michael Kolbe3de7222013-02-18 15:16:44 -08001006 if (mAnimation != null && !mAnimation.hasEnded()) {
Michael Kolb8872c232013-01-29 10:33:22 -08001007 mAnimation.cancel();
1008 }
1009 mFocusCancelled = false;
1010 mFocused = false;
1011 mState = STATE_IDLE;
1012 }
1013
Sascha Haeberlingb7b73ec2013-11-12 15:16:08 -08001014 public void clear(boolean waitUntilProgressIsHidden) {
1015 if (mState == STATE_PIE)
1016 return;
1017 cancelFocus();
Erin Dahlgren5ce035e2013-12-10 17:16:17 -08001018 mOverlay.post(mDisappear);
Sascha Haeberlingb7b73ec2013-11-12 15:16:08 -08001019 }
1020
Michael Kolb8872c232013-01-29 10:33:22 -08001021 @Override
1022 public void clear() {
Sascha Haeberlingb7b73ec2013-11-12 15:16:08 -08001023 clear(false);
Michael Kolb8872c232013-01-29 10:33:22 -08001024 }
1025
1026 private void startAnimation(long duration, boolean timeout,
1027 float toScale) {
1028 startAnimation(duration, timeout, mDialAngle,
1029 toScale);
1030 }
1031
1032 private void startAnimation(long duration, boolean timeout,
1033 float fromScale, float toScale) {
1034 setVisible(true);
1035 mAnimation.reset();
1036 mAnimation.setDuration(duration);
1037 mAnimation.setScale(fromScale, toScale);
1038 mAnimation.setAnimationListener(timeout ? mEndAction : null);
1039 mOverlay.startAnimation(mAnimation);
1040 update();
1041 }
1042
1043 private class EndAction implements Animation.AnimationListener {
1044 @Override
1045 public void onAnimationEnd(Animation animation) {
1046 // Keep the focus indicator for some time.
Doris Liuf55f3c42013-11-20 00:24:46 -08001047 if (!mFocusCancelled && mOverlay != null) {
Michael Kolb8872c232013-01-29 10:33:22 -08001048 mOverlay.postDelayed(mDisappear, DISAPPEAR_TIMEOUT);
1049 }
1050 }
1051
1052 @Override
1053 public void onAnimationRepeat(Animation animation) {
1054 }
1055
1056 @Override
1057 public void onAnimationStart(Animation animation) {
1058 }
1059 }
1060
1061 private class Disappear implements Runnable {
1062 @Override
1063 public void run() {
1064 if (mState == STATE_PIE) return;
1065 setVisible(false);
1066 mFocusX = mCenterX;
1067 mFocusY = mCenterY;
1068 mState = STATE_IDLE;
1069 setCircle(mFocusX, mFocusY);
1070 mFocused = false;
1071 }
1072 }
1073
Michael Kolb8872c232013-01-29 10:33:22 -08001074 private class ScaleAnimation extends Animation {
1075 private float mFrom = 1f;
1076 private float mTo = 1f;
1077
1078 public ScaleAnimation() {
1079 setFillAfter(true);
1080 }
1081
1082 public void setScale(float from, float to) {
1083 mFrom = from;
1084 mTo = to;
1085 }
1086
1087 @Override
1088 protected void applyTransformation(float interpolatedTime, Transformation t) {
1089 mDialAngle = (int)(mFrom + (mTo - mFrom) * interpolatedTime);
1090 }
1091 }
1092
Michael Kolb8872c232013-01-29 10:33:22 -08001093}