From 3bc96b2d1106fc5ebec6fda6aad3bca4d62e81c0 Mon Sep 17 00:00:00 2001 From: Michael Kolb Date: Tue, 12 Mar 2013 10:24:42 -0700 Subject: Pie design update Change-Id: Ifb0d50938332bdae50e45523850605d0aafed7fb --- src/com/android/camera/PhotoMenu.java | 45 +++-- src/com/android/camera/PhotoUI.java | 33 ++- src/com/android/camera/PieController.java | 27 ++- src/com/android/camera/PreviewGestures.java | 50 +++-- src/com/android/camera/VideoMenu.java | 14 +- src/com/android/camera/VideoUI.java | 19 +- src/com/android/camera/ui/PieItem.java | 8 +- src/com/android/camera/ui/PieRenderer.java | 300 +++++++++++++++++++--------- 8 files changed, 343 insertions(+), 153 deletions(-) diff --git a/src/com/android/camera/PhotoMenu.java b/src/com/android/camera/PhotoMenu.java index 2535a0cc4..280ad44a9 100644 --- a/src/com/android/camera/PhotoMenu.java +++ b/src/com/android/camera/PhotoMenu.java @@ -34,7 +34,6 @@ public class PhotoMenu extends PieController TimerSettingPopup.Listener, ListPrefSettingPopup.Listener { private static String TAG = "CAM_photomenu"; - private static float FLOAT_PI_DIVIDED_BY_TWO = (float) Math.PI / 2; private final String mSettingOff; private PhotoUI mUI; @@ -54,13 +53,20 @@ public class PhotoMenu extends PieController super.initialize(group); mPopup = null; mSecondPopup = null; - float sweep = FLOAT_PI_DIVIDED_BY_TWO / 2; - addItem(CameraSettings.KEY_FLASH_MODE, FLOAT_PI_DIVIDED_BY_TWO - sweep, sweep); - addItem(CameraSettings.KEY_EXPOSURE, 3 * FLOAT_PI_DIVIDED_BY_TWO - sweep, sweep); - addItem(CameraSettings.KEY_WHITE_BALANCE, 3 * FLOAT_PI_DIVIDED_BY_TWO + sweep, sweep); + float sweep = (float) (SWEEP * Math.PI); + PieItem item = null; + // flash + if (group.findPreference(CameraSettings.KEY_FLASH_MODE) != null) { + item = makeItem(CameraSettings.KEY_FLASH_MODE, CENTER - sweep, sweep); + mRenderer.addItem(item); + } + // exposure compensation + item = makeItem(CameraSettings.KEY_EXPOSURE, CENTER + sweep, sweep); + mRenderer.addItem(item); + // camera switcher if (group.findPreference(CameraSettings.KEY_CAMERA_ID) != null) { - PieItem item = makeItem(R.drawable.ic_switch_photo_facing_holo_light); - item.setFixedSlice(FLOAT_PI_DIVIDED_BY_TWO + sweep, sweep); + item = makeItem(R.drawable.ic_switch_photo_facing_holo_light); + item.setFixedSlice(CENTER - 2 * sweep, sweep); item.setOnClickListener(new OnClickListener() { @Override public void onClick(PieItem item) { @@ -79,10 +85,11 @@ public class PhotoMenu extends PieController }); mRenderer.addItem(item); } + // hdr if (group.findPreference(CameraSettings.KEY_CAMERA_HDR) != null) { - PieItem hdr = makeItem(R.drawable.ic_hdr); - hdr.setFixedSlice(FLOAT_PI_DIVIDED_BY_TWO, sweep); - hdr.setOnClickListener(new OnClickListener() { + item = makeItem(R.drawable.ic_hdr); + item.setFixedSlice(CENTER + 2 * sweep, sweep); + item.setOnClickListener(new OnClickListener() { @Override public void onClick(PieItem item) { // Find the index of next camera. @@ -96,8 +103,18 @@ public class PhotoMenu extends PieController } } }); - mRenderer.addItem(hdr); + mRenderer.addItem(item); } + + // more settings + PieItem more = makeItem(R.drawable.ic_settings_holo_light); + more.setFixedSlice(CENTER, sweep); + mRenderer.addItem(more); + // white balance + item = makeItem(CameraSettings.KEY_WHITE_BALANCE, + CENTER + sweep, sweep); + more.addItem(item); + // settings popup mOtherKeys = new String[] { CameraSettings.KEY_SCENE_MODE, CameraSettings.KEY_RECORD_LOCATION, @@ -106,8 +123,8 @@ public class PhotoMenu extends PieController CameraSettings.KEY_TIMER, CameraSettings.KEY_TIMER_SOUND_EFFECTS, }; - PieItem item = makeItem(R.drawable.ic_settings_holo_light); - item.setFixedSlice(FLOAT_PI_DIVIDED_BY_TWO * 3, sweep); + item = makeItem(R.drawable.ic_settings_holo_light); + item.setFixedSlice(CENTER, sweep); item.setOnClickListener(new OnClickListener() { @Override public void onClick(PieItem item) { @@ -117,7 +134,7 @@ public class PhotoMenu extends PieController mUI.showPopup(mPopup); } }); - mRenderer.addItem(item); + more.addItem(item); } @Override diff --git a/src/com/android/camera/PhotoUI.java b/src/com/android/camera/PhotoUI.java index de0468b3c..1af870abc 100644 --- a/src/com/android/camera/PhotoUI.java +++ b/src/com/android/camera/PhotoUI.java @@ -56,7 +56,8 @@ public class PhotoUI implements PieListener, PreviewGestures.SingleTapListener, FocusUI, LocationManager.Listener, - FaceDetectionListener { + FaceDetectionListener, + PreviewGestures.SwipeListener { private static final String TAG = "CAM_UI"; @@ -175,7 +176,8 @@ public class PhotoUI implements PieListener, } if (mGestures == null) { // this will handle gesture disambiguation and dispatching - mGestures = new PreviewGestures(mActivity, this, mZoomRenderer, mPieRenderer); + mGestures = new PreviewGestures(mActivity, this, mZoomRenderer, mPieRenderer, + this); } mGestures.clearTouchReceivers(); mGestures.setRenderOverlay(mRenderOverlay); @@ -196,20 +198,24 @@ public class PhotoUI implements PieListener, updateOnScreenIndicators(params, prefs); } + private void openMenu() { + if (mPieRenderer != null) { + // If autofocus is not finished, cancel autofocus so that the + // subsequent touch can be handled by PreviewGestures + if (mController.getCameraState() == PhotoController.FOCUSING) { + mController.cancelAutoFocus(); + } + mPieRenderer.showInCenter(); + } + } + public void initializeControlByIntent() { mBlocker = mActivity.findViewById(R.id.blocker); mMenuButton = mActivity.findViewById(R.id.menu); mMenuButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - if (mPieRenderer != null) { - // If autofocus is not finished, cancel autofocus so that the - // subsequent touch can be handled by PreviewGestures - if (mController.getCameraState() == PhotoController.FOCUSING) { - mController.cancelAutoFocus(); - } - mPieRenderer.showInCenter(); - } + openMenu(); } }); if (mController.isImageCaptureIntent()) { @@ -723,4 +729,11 @@ public class PhotoUI implements PieListener, mFaceView.setFaces(faces); } + @Override + public void onSwipe(int direction) { + if (direction == PreviewGestures.DIR_UP) { + openMenu(); + } + } + } diff --git a/src/com/android/camera/PieController.java b/src/com/android/camera/PieController.java index 8202fca21..2145fd894 100644 --- a/src/com/android/camera/PieController.java +++ b/src/com/android/camera/PieController.java @@ -37,6 +37,10 @@ public class PieController { protected static final int MODE_PHOTO = 0; protected static final int MODE_VIDEO = 1; + protected static float CENTER = (float) Math.PI / 2; + protected static final float SWEEP = 0.06f; + + protected CameraActivity mActivity; protected PreferenceGroup mPreferenceGroup; protected OnPreferenceChangedListener mListener; @@ -84,10 +88,10 @@ public class PieController { return new PieItem(drawable, 0); } - public void addItem(String prefKey, float center, float sweep) { + public PieItem makeItem(String prefKey, float center, float sweep) { final IconListPreference pref = (IconListPreference) mPreferenceGroup.findPreference(prefKey); - if (pref == null) return; + if (pref == null) return null; int[] iconIds = pref.getLargeIconIds(); int resid = -1; if (!pref.getUseSingleIcon() && iconIds != null) { @@ -101,7 +105,6 @@ public class PieController { PieItem item = makeItem(resid); // use center and sweep to determine layout item.setFixedSlice(center, sweep); - mRenderer.addItem(item); mPreferences.add(pref); mPreferenceMap.put(pref, item); int nOfEntries = pref.getEntries().length; @@ -113,6 +116,7 @@ public class PieController { } else { inner = makeItem(pref.getEntries()[i]); } + layoutInner(inner, i, nOfEntries); item.addItem(inner); final int index = i; inner.setOnClickListener(new OnClickListener() { @@ -125,6 +129,23 @@ public class PieController { }); } } + return item; + } + + public PieItem makeDialItem(ListPreference pref, int iconId, float center, float sweep) { + PieItem item = makeItem(iconId); + return item; + } + + protected void layoutInner(PieItem item, int ix, int n) { + float sweep = (float) (SWEEP * Math.PI);//FLOAT_PI_DIVIDED_BY_TWO / Math.max(n, 5); + float start = CENTER + (n - 1) * (sweep / 2f); + item.setFixedSlice(start - ix * sweep, sweep); + } + + public void addItem(String prefKey, float center, float sweep) { + PieItem item = makeItem(prefKey, center, sweep); + mRenderer.addItem(item); } public void setPreferenceGroup(PreferenceGroup group) { diff --git a/src/com/android/camera/PreviewGestures.java b/src/com/android/camera/PreviewGestures.java index f6b622827..351da0afd 100644 --- a/src/com/android/camera/PreviewGestures.java +++ b/src/com/android/camera/PreviewGestures.java @@ -18,7 +18,6 @@ package com.android.camera; import android.os.Handler; import android.os.Message; -import android.util.Log; import android.view.MotionEvent; import android.view.ScaleGestureDetector; import android.view.View; @@ -44,6 +43,12 @@ public class PreviewGestures private static final int MODE_ZOOM = 2; private static final int MODE_MODULE = 3; private static final int MODE_ALL = 4; + private static final int MODE_SWIPE = 5; + + public static final int DIR_UP = 0; + public static final int DIR_DOWN = 1; + public static final int DIR_LEFT = 2; + public static final int DIR_RIGHT = 3; private CameraActivity mActivity; private SingleTapListener mTapListener; @@ -61,6 +66,7 @@ public class PreviewGestures private boolean mZoomOnly; private int mOrientation; private int[] mLocation; + private SwipeListener mSwipeListener; private Handler mHandler = new Handler() { public void handleMessage(Message msg) { @@ -76,8 +82,12 @@ public class PreviewGestures public void onSingleTapUp(View v, int x, int y); } + interface SwipeListener { + public void onSwipe(int direction); + } + public PreviewGestures(CameraActivity ctx, SingleTapListener tapListener, - ZoomRenderer zoom, PieRenderer pie) { + ZoomRenderer zoom, PieRenderer pie, SwipeListener swipe) { mActivity = ctx; mTapListener = tapListener; mPie = pie; @@ -88,6 +98,7 @@ public class PreviewGestures mTapTimeout = ViewConfiguration.getTapTimeout(); mEnabled = true; mLocation = new int[2]; + mSwipeListener = swipe; } public void setRenderOverlay(RenderOverlay overlay) { @@ -149,6 +160,11 @@ public class PreviewGestures } } else if (mMode == MODE_NONE) { return false; + } else if (mMode == MODE_SWIPE) { + if (MotionEvent.ACTION_UP == m.getActionMasked()) { + mSwipeListener.onSwipe(getSwipeDirection(m)); + } + return true; } else if (mMode == MODE_PIE) { if (MotionEvent.ACTION_POINTER_DOWN == m.getActionMasked()) { sendToPie(makeCancelEvent(m)); @@ -215,18 +231,13 @@ public class PreviewGestures || Math.abs(m.getY() - mDown.getY()) > mSlop) { // moved too far and no timeout yet, no focus or pie cancelPie(); - if (isSwipe(m, true)) { + int dir = getSwipeDirection(m); + if (dir == DIR_LEFT) { mMode = MODE_MODULE; return mActivity.superDispatchTouchEvent(m); } else { cancelActivityTouchHandling(m); - if (isSwipe(m , false)) { - mMode = MODE_NONE; - } else if (!mZoomOnly) { - mMode = MODE_PIE; - openPie(); - sendToPie(m); - } + mMode = MODE_NONE; } } } @@ -246,32 +257,31 @@ public class PreviewGestures } // left tests for finger moving right to left - private boolean isSwipe(MotionEvent m, boolean left) { + private int getSwipeDirection(MotionEvent m) { float dx = 0; float dy = 0; switch (mOrientation) { case 0: dx = m.getX() - mDown.getX(); - dy = Math.abs(m.getY() - mDown.getY()); + dy = m.getY() - mDown.getY(); break; case 90: dx = - (m.getY() - mDown.getY()); - dy = Math.abs(m.getX() - mDown.getX()); + dy = m.getX() - mDown.getX(); break; case 180: dx = -(m.getX() - mDown.getX()); - dy = Math.abs(m.getY() - mDown.getY()); + dy = m.getY() - mDown.getY(); break; case 270: dx = m.getY() - mDown.getY(); - dy = Math.abs(m.getX() - mDown.getX()); + dy = m.getX() - mDown.getX(); break; } - if (left) { - return (dx < 0 && dy / -dx < 0.6f); - } else { - return (dx > 0 && dy / dx < 0.6f); - } + if (dx < 0 && (Math.abs(dy) / -dx < 2)) return DIR_LEFT; + if (dx > 0 && (Math.abs(dy) / dx < 2)) return DIR_RIGHT; + if (dy > 0) return DIR_DOWN; + return DIR_UP; } private boolean isInside(MotionEvent evt, View v) { diff --git a/src/com/android/camera/VideoMenu.java b/src/com/android/camera/VideoMenu.java index aa3a80716..0f987aa87 100644 --- a/src/com/android/camera/VideoMenu.java +++ b/src/com/android/camera/VideoMenu.java @@ -34,7 +34,6 @@ public class VideoMenu extends PieController TimeIntervalPopup.Listener { private static String TAG = "CAM_VideoMenu"; - private static float FLOAT_PI_DIVIDED_BY_TWO = (float) Math.PI / 2; private VideoUI mUI; private String[] mOtherKeys; @@ -54,12 +53,13 @@ public class VideoMenu extends PieController super.initialize(group); mPopup = null; mPopupStatus = POPUP_NONE; - float sweep = FLOAT_PI_DIVIDED_BY_TWO / 2; + float sweep = (float)(SWEEP * Math.PI); - addItem(CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE, FLOAT_PI_DIVIDED_BY_TWO - sweep, sweep); - addItem(CameraSettings.KEY_WHITE_BALANCE, 3 * FLOAT_PI_DIVIDED_BY_TWO + sweep, sweep); + addItem(CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE, CENTER - sweep, sweep); + addItem(CameraSettings.KEY_WHITE_BALANCE, CENTER + sweep, sweep); + // camera switcher PieItem item = makeItem(R.drawable.ic_switch_video_facing_holo_light); - item.setFixedSlice(FLOAT_PI_DIVIDED_BY_TWO + sweep, sweep); + item.setFixedSlice(CENTER - 2 * sweep, sweep); item.setOnClickListener(new OnClickListener() { @Override @@ -76,15 +76,15 @@ public class VideoMenu extends PieController } }); mRenderer.addItem(item); + // settings popup mOtherKeys = new String[] { CameraSettings.KEY_VIDEO_EFFECT, CameraSettings.KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL, CameraSettings.KEY_VIDEO_QUALITY, CameraSettings.KEY_RECORD_LOCATION }; - item = makeItem(R.drawable.ic_settings_holo_light); - item.setFixedSlice(FLOAT_PI_DIVIDED_BY_TWO * 3, sweep); + item.setFixedSlice(CENTER, sweep); item.setOnClickListener(new OnClickListener() { @Override public void onClick(PieItem item) { diff --git a/src/com/android/camera/VideoUI.java b/src/com/android/camera/VideoUI.java index 0eac49d9b..bb615e9ec 100644 --- a/src/com/android/camera/VideoUI.java +++ b/src/com/android/camera/VideoUI.java @@ -44,7 +44,8 @@ import com.android.gallery3d.common.ApiHelper; import java.util.List; public class VideoUI implements SurfaceHolder.Callback, PieRenderer.PieListener, - PreviewGestures.SingleTapListener { + PreviewGestures.SingleTapListener, + PreviewGestures.SwipeListener { private final static String TAG = "CAM_VideoUI"; // module fields private CameraActivity mActivity; @@ -210,7 +211,7 @@ public class VideoUI implements SurfaceHolder.Callback, PieRenderer.PieListener, } mRenderOverlay.addRenderer(mZoomRenderer); if (mGestures == null) { - mGestures = new PreviewGestures(mActivity, this, mZoomRenderer, mPieRenderer); + mGestures = new PreviewGestures(mActivity, this, mZoomRenderer, mPieRenderer, this); } mGestures.setRenderOverlay(mRenderOverlay); mGestures.clearTouchReceivers(); @@ -279,6 +280,12 @@ public class VideoUI implements SurfaceHolder.Callback, PieRenderer.PieListener, } } + private void openMenu() { + if (mPieRenderer != null) { + mPieRenderer.showInCenter(); + } + } + public void showPopup(AbstractSettingPopup popup) { mActivity.hideUI(); mBlocker.setVisibility(View.INVISIBLE); @@ -513,4 +520,12 @@ public class VideoUI implements SurfaceHolder.Callback, PieRenderer.PieListener, public void onZoomEnd() { } } + + @Override + public void onSwipe(int direction) { + if (direction == PreviewGestures.DIR_UP) { + openMenu(); + } + } + } diff --git a/src/com/android/camera/ui/PieItem.java b/src/com/android/camera/ui/PieItem.java index 677e5acc8..bbfa1dc82 100644 --- a/src/com/android/camera/ui/PieItem.java +++ b/src/com/android/camera/ui/PieItem.java @@ -57,7 +57,9 @@ public class PieItem { public PieItem(Drawable drawable, int level) { mDrawable = drawable; this.level = level; - setAlpha(1f); + if (drawable != null) { + setAlpha(1f); + } mEnabled = true; setAnimationAngle(getAnimationAngle()); start = -1; @@ -79,6 +81,10 @@ public class PieItem { mItems.add(item); } + public void clearItems() { + mItems = null; + } + public void setPath(Path p) { mPath = p; } diff --git a/src/com/android/camera/ui/PieRenderer.java b/src/com/android/camera/ui/PieRenderer.java index b60d9f668..dffa47e1e 100644 --- a/src/com/android/camera/ui/PieRenderer.java +++ b/src/com/android/camera/ui/PieRenderer.java @@ -27,6 +27,7 @@ import android.graphics.PointF; import android.graphics.RectF; import android.os.Handler; import android.os.Message; +import android.util.Log; import android.view.MotionEvent; import android.view.ViewConfiguration; import android.view.animation.Animation; @@ -54,6 +55,8 @@ public class PieRenderer extends OverlayRenderer private static final int STATE_FINISHING = 2; private static final int STATE_PIE = 8; + private static final float MATH_PI_2 = (float)(Math.PI / 2); + private Runnable mDisappear = new Disappear(); private Animation.AnimationListener mEndAction = new EndAction(); private static final int SCALING_UP_TIME = 600; @@ -66,26 +69,26 @@ public class PieRenderer extends OverlayRenderer private static final long PIE_FADE_IN_DURATION = 200; private static final long PIE_XFADE_DURATION = 200; private static final long PIE_SELECT_FADE_DURATION = 300; + private static final long PIE_OPEN_SUB_DELAY = 400; private static final int MSG_OPEN = 0; private static final int MSG_CLOSE = 1; - private static final float PIE_SWEEP = (float)(Math.PI * 2 / 3); + private static final int MSG_OPENSUBMENU = 2; + private static final float PIE_SWEEP = (float)(Math.PI / 2); // geometry - private Point mCenter; + private Point mSliceCenter; private int mRadius; - private int mRadiusInc; // the detection if touch is inside a slice is offset // inbounds by this amount to allow the selection to show before the // finger covers it private int mTouchOffset; - private List mItems; - - private PieItem mOpenItem; + private List mOpen; private Paint mSelectedPaint; private Paint mSubPaint; + private Paint mMenuArcPaint; // touch handling private PieItem mCurrentItem; @@ -98,6 +101,11 @@ public class PieRenderer extends OverlayRenderer private int mFocusY; private int mCenterX; private int mCenterY; + private int mPieCenterX; + private int mPieCenterY; + private int mIconRadius; + private int mArcRadius; + private int mArcOffset; private int mDialAngle; private RectF mCircle; @@ -124,7 +132,7 @@ public class PieRenderer extends OverlayRenderer switch(msg.what) { case MSG_OPEN: if (mListener != null) { - mListener.onPieOpened(mCenter.x, mCenter.y); + mListener.onPieOpened(mPieCenterX, mPieCenterY); } break; case MSG_CLOSE: @@ -132,7 +140,11 @@ public class PieRenderer extends OverlayRenderer mListener.onPieClosed(); } break; + case MSG_OPENSUBMENU: + onEnterOpen(); + break; } + } }; @@ -153,13 +165,13 @@ public class PieRenderer extends OverlayRenderer private void init(Context ctx) { setVisible(false); - mItems = new ArrayList(); + mOpen = new ArrayList(); + mOpen.add(new PieItem(null, 0)); Resources res = ctx.getResources(); mRadius = (int) res.getDimensionPixelSize(R.dimen.pie_radius_start); mCircleSize = mRadius - res.getDimensionPixelSize(R.dimen.focus_radius_offset); - mRadiusInc = (int) res.getDimensionPixelSize(R.dimen.pie_radius_increment); mTouchOffset = (int) res.getDimensionPixelSize(R.dimen.pie_touch_offset); - mCenter = new Point(0,0); + mSliceCenter = new Point(0,0); mSelectedPaint = new Paint(); mSelectedPaint.setColor(Color.argb(255, 51, 181, 229)); mSelectedPaint.setAntiAlias(true); @@ -184,6 +196,18 @@ public class PieRenderer extends OverlayRenderer mTouchSlopSquared = ViewConfiguration.get(ctx).getScaledTouchSlop(); mTouchSlopSquared = mTouchSlopSquared * mTouchSlopSquared; mDown = new Point(); + mMenuArcPaint = new Paint(); + mMenuArcPaint.setAntiAlias(true); + mMenuArcPaint.setColor(Color.argb(140, 255, 255, 255)); + mMenuArcPaint.setStrokeWidth(10); + mMenuArcPaint.setStyle(Paint.Style.STROKE); + mIconRadius = res.getDimensionPixelSize(R.dimen.pie_item_radius); + mArcRadius = res.getDimensionPixelSize(R.dimen.pie_arc_radius); + mArcOffset = res.getDimensionPixelSize(R.dimen.pie_arc_offset); + } + + private PieItem getRoot() { + return mOpen.get(0); } public boolean showsItems() { @@ -192,15 +216,11 @@ public class PieRenderer extends OverlayRenderer public void addItem(PieItem item) { // add the item to the pie itself - mItems.add(item); - } - - public void removeItem(PieItem item) { - mItems.remove(item); + getRoot().addItem(item); } public void clearItems() { - mItems.clear(); + getRoot().clearItems(); } public void showInCenter() { @@ -212,7 +232,8 @@ public class PieRenderer extends OverlayRenderer cancelFocus(); } mState = STATE_PIE; - setCenter(mCenterX, mCenterY); + resetPieCenter(); + setCenter(mPieCenterX, mPieCenterY); mTapMode = true; show(true); } @@ -231,10 +252,16 @@ public class PieRenderer extends OverlayRenderer mState = STATE_PIE; // ensure clean state mCurrentItem = null; - mOpenItem = null; - for (PieItem item : mItems) { - item.setSelected(false); + PieItem root = getRoot(); + for (PieItem openItem : mOpen) { + if (openItem.hasItems()) { + for (PieItem item : openItem.getItems()) { + item.setSelected(false); + } + } } + mOpen.clear(); + mOpen.add(root); layoutPie(); fadeIn(); } else { @@ -270,22 +297,41 @@ public class PieRenderer extends OverlayRenderer } public void setCenter(int x, int y) { - mCenter.x = x; - mCenter.y = y; - // when using the pie menu, align the focus ring - alignFocus(x, y); + mPieCenterX = x; + mPieCenterY = y; + mSliceCenter.x = x; + mSliceCenter.y = y - mArcOffset + mIconRadius; + } + + @Override + public void layout(int l, int t, int r, int b) { + super.layout(l, t, r, b); + mCenterX = (r - l) / 2; + mCenterY = (b - t) / 2; + + mFocusX = mCenterX; + mFocusY = mCenterY; + resetPieCenter(); + setCircle(mFocusX, mFocusY); + if (isVisible() && mState == STATE_PIE) { + setCenter(mPieCenterX, mPieCenterY); + layoutPie(); + } + } + + private void resetPieCenter() { + mPieCenterX = mCenterX; + mPieCenterY = mCenterY + mCenterY / 3; } private void layoutPie() { - int rgap = 2; - int inner = mRadius + rgap; - int outer = mRadius + mRadiusInc - rgap; + int inner = mIconRadius; + int outer = inner + mTouchOffset; int gap = 1; - layoutItems(mItems, (float) (Math.PI / 2), inner, outer, gap); + layoutItems(0, getRoot().getItems(), (float) (Math.PI / 2), inner, outer, gap); } - private void layoutItems(List items, float centerAngle, int inner, - int outer, int gap) { + private void layoutItems(int level, List items, float centerAngle, int inner, int outer, int gap) { float emptyangle = PIE_SWEEP / 16; float sweep = (float) (PIE_SWEEP - 2 * emptyangle) / items.size(); float angle = centerAngle - PIE_SWEEP / 2 + emptyangle + sweep / 2; @@ -298,8 +344,10 @@ public class PieRenderer extends OverlayRenderer break; } } + Point p = new Point(mSliceCenter); + p.y -= level * mTouchOffset; Path path = makeSlice(getDegrees(0) - gap, getDegrees(sweep) + gap, - outer, inner, mCenter); + outer, inner, p); for (PieItem item : items) { // shared between items item.setPath(path); @@ -311,14 +359,14 @@ public class PieRenderer extends OverlayRenderer // move views to outer border int r = inner + (outer - inner) * 2 / 3; int x = (int) (r * Math.cos(angle)); - int y = mCenter.y - (int) (r * Math.sin(angle)) - h / 2; - x = mCenter.x + x - w / 2; + int y = mSliceCenter.y - (level * mTouchOffset) - (int) (r * Math.sin(angle)) - h / 2; + x = mSliceCenter.x + x - w / 2; item.setBounds(x, y, x + w, y + h); float itemstart = angle - sweep / 2; item.setGeometry(itemstart, sweep, inner, outer); if (item.hasItems()) { - layoutItems(item.getItems(), angle, inner, - outer + mRadiusInc / 2, gap); + layoutItems(level + 1, item.getItems(), MATH_PI_2, inner, + outer, gap); } angle += sweep; } @@ -378,6 +426,31 @@ public class PieRenderer extends OverlayRenderer mOverlay.startAnimation(mFadeOut); } + // root does not count + private boolean hasOpenItem() { + return mOpen.size() > 1; + } + + // pop an item of the open item stack + private PieItem closeOpenItem() { + PieItem item = getOpenItem(); + mOpen.remove(mOpen.size() -1); + return item; + } + + private PieItem getOpenItem() { + return mOpen.get(mOpen.size() - 1); + } + + // return the children either the root or parent of the current open item + private PieItem getParent() { + return mOpen.get(Math.max(0, mOpen.size() - 2)); + } + + private int getLevel() { + return mOpen.size() - 1; + } + @Override public void onDraw(Canvas canvas) { float alpha = 1; @@ -391,39 +464,55 @@ public class PieRenderer extends OverlayRenderer int state = canvas.save(); if (mFadeIn != null) { float sf = 0.9f + alpha * 0.1f; - canvas.scale(sf, sf, mCenter.x, mCenter.y); + canvas.scale(sf, sf, mPieCenterX, mPieCenterY); + } + if (mState != STATE_PIE) { + drawFocus(canvas); } - drawFocus(canvas); if (mState == STATE_FINISHING) { canvas.restoreToCount(state); return; } - if ((mOpenItem == null) || (mXFade != null)) { + if (!hasOpenItem() || (mXFade != null)) { // draw base menu - for (PieItem item : mItems) { - drawItem(canvas, item, alpha); + drawArc(canvas, getLevel()); + for (PieItem item : getParent().getItems()) { + drawItem(Math.max(0, mOpen.size() - 2), canvas, item, alpha); } } - if (mOpenItem != null) { - for (PieItem inner : mOpenItem.getItems()) { + if (hasOpenItem()) { + int level = getLevel(); + drawArc(canvas, level); + for (PieItem inner : getOpenItem().getItems()) { if (mFadeOut != null) { - drawItem(canvas, inner, alpha); + drawItem(level, canvas, inner, alpha); } else { - drawItem(canvas, inner, (mXFade != null) ? (1 - 0.5f * alpha) : 1); + drawItem(level, canvas, inner, (mXFade != null) ? (1 - 0.5f * alpha) : 1); } } } canvas.restoreToCount(state); } - private void drawItem(Canvas canvas, PieItem item, float alpha) { + private void drawArc(Canvas canvas, int level) { + // arc + if (mState == STATE_PIE) { + int nr = mArcRadius; + int cy = mPieCenterY - mArcOffset + mArcRadius - level * mTouchOffset; + canvas.drawArc(new RectF(mPieCenterX - nr, cy - mArcRadius, + mPieCenterX + nr, cy + mArcRadius), + 252, 36, false, mMenuArcPaint); + } + } + private void drawItem(int level, Canvas canvas, PieItem item, float alpha) { if (mState == STATE_PIE) { if (item.getPath() != null) { + int y = mSliceCenter.y - level * mTouchOffset; if (item.isSelected()) { Paint p = mSelectedPaint; int state = canvas.save(); float r = getDegrees(item.getStartAngle()); - canvas.rotate(r, mCenter.x, mCenter.y); + canvas.rotate(r, mSliceCenter.x, y); if (mFadeOut != null) { p.setAlpha((int)(255 * alpha)); } @@ -448,7 +537,7 @@ public class PieRenderer extends OverlayRenderer float x = evt.getX(); float y = evt.getY(); int action = evt.getActionMasked(); - PointF polar = getPolar(x, y, !(mTapMode)); + PointF polar = getPolar(x, y, !mTapMode); if (MotionEvent.ACTION_DOWN == action) { mDown.x = (int) evt.getX(); mDown.y = (int) evt.getY(); @@ -469,7 +558,7 @@ public class PieRenderer extends OverlayRenderer PieItem item = mCurrentItem; if (mTapMode) { item = findItem(polar); - if (item != null && mOpening) { + if (mOpening) { mOpening = false; return true; } @@ -477,10 +566,11 @@ public class PieRenderer extends OverlayRenderer if (item == null) { mTapMode = false; show(false); - } else if (!mOpening - && !item.hasItems()) { - startFadeOut(item); - mTapMode = false; + } else if (!mOpening && !item.hasItems()) { + startFadeOut(item); + mTapMode = false; + } else { + mTapMode = true; } return true; } @@ -489,11 +579,17 @@ public class PieRenderer extends OverlayRenderer show(false); } deselect(); + mHandler.removeMessages(MSG_OPENSUBMENU); return false; } else if (MotionEvent.ACTION_MOVE == action) { - if (polar.y < mRadius) { - if (mOpenItem != null) { - mOpenItem = null; + if (pulledToCenter(polar)) { + mHandler.removeMessages(MSG_OPENSUBMENU); + if (hasOpenItem()) { + if (mCurrentItem != null) { + mCurrentItem.setSelected(false); + } + closeOpenItem(); + mCurrentItem = null; } else { deselect(); } @@ -502,23 +598,63 @@ public class PieRenderer extends OverlayRenderer PieItem item = findItem(polar); boolean moved = hasMoved(evt); if ((item != null) && (mCurrentItem != item) && (!mOpening || moved)) { + mHandler.removeMessages(MSG_OPENSUBMENU); // only select if we didn't just open or have moved past slop - mOpening = false; if (moved) { // switch back to swipe mode mTapMode = false; } - onEnter(item); + onEnterSelect(item); + mHandler.sendEmptyMessageDelayed(MSG_OPENSUBMENU, PIE_OPEN_SUB_DELAY); } } return false; } + private boolean pulledToCenter(PointF polarCoords) { + return polarCoords.y < mIconRadius - mArcOffset; + } + + private PointF getPolar(float x, float y, boolean useOffset) { + PointF res = new PointF(); + // get angle and radius from x/y + res.x = (float) Math.PI / 2; + x = x - mSliceCenter.x; + y = mSliceCenter.y - getLevel() * mTouchOffset - y; + res.y = (float) Math.sqrt(x * x + y * y); + if (x != 0) { + res.x = (float) Math.atan2(y, x); + if (res.x < 0) { + res.x = (float) (2 * Math.PI + res.x); + } + } + res.y = res.y + (useOffset ? mTouchOffset : 0); + return res; + } + private boolean hasMoved(MotionEvent e) { return mTouchSlopSquared < (e.getX() - mDown.x) * (e.getX() - mDown.x) + (e.getY() - mDown.y) * (e.getY() - mDown.y); } + private void onEnterSelect(PieItem item) { + if (mCurrentItem != null) { + mCurrentItem.setSelected(false); + } + if (item != null && item.isEnabled()) { + item.setSelected(true); + mCurrentItem = item; + } else { + mCurrentItem = null; + } + } + + private void onEnterOpen() { + if ((mCurrentItem != getOpenItem()) && mCurrentItem.hasItems()) { + openCurrentItem(); + } + } + /** * enter a slice for a view * updates model only @@ -531,7 +667,7 @@ public class PieRenderer extends OverlayRenderer if (item != null && item.isEnabled()) { item.setSelected(true); mCurrentItem = item; - if ((mCurrentItem != mOpenItem) && mCurrentItem.hasItems()) { + if ((mCurrentItem != getOpenItem()) && mCurrentItem.hasItems()) { openCurrentItem(); } } else { @@ -543,22 +679,24 @@ public class PieRenderer extends OverlayRenderer if (mCurrentItem != null) { mCurrentItem.setSelected(false); } - if (mOpenItem != null) { - mOpenItem = null; + if (hasOpenItem()) { + PieItem item = closeOpenItem(); + onEnter(item); + } else { + mCurrentItem = null; } - mCurrentItem = null; } private void openCurrentItem() { if ((mCurrentItem != null) && mCurrentItem.hasItems()) { - mCurrentItem.setSelected(false); - mOpenItem = mCurrentItem; + mOpen.add(mCurrentItem); mOpening = true; if (mFadeIn != null) { mFadeIn.cancel(); } mXFade = new LinearAnimation(1, 0); mXFade.setDuration(PIE_XFADE_DURATION); + final PieItem ci = mCurrentItem; mXFade.setAnimationListener(new AnimationListener() { @Override public void onAnimationStart(Animation animation) { @@ -567,6 +705,8 @@ public class PieRenderer extends OverlayRenderer @Override public void onAnimationEnd(Animation animation) { mXFade = null; + ci.setSelected(false); + mOpening = false; } @Override @@ -578,30 +718,13 @@ public class PieRenderer extends OverlayRenderer } } - private PointF getPolar(float x, float y, boolean useOffset) { - PointF res = new PointF(); - // get angle and radius from x/y - res.x = (float) Math.PI / 2; - x = x - mCenter.x; - y = mCenter.y - y; - res.y = (float) Math.sqrt(x * x + y * y); - if (x != 0) { - res.x = (float) Math.atan2(y, x); - if (res.x < 0) { - res.x = (float) (2 * Math.PI + res.x); - } - } - res.y = res.y + (useOffset ? mTouchOffset : 0); - return res; - } - /** * @param polar x: angle, y: dist * @return the item at angle/dist or null */ private PieItem findItem(PointF polar) { // find the matching item: - List items = (mOpenItem != null) ? mOpenItem.getItems() : mItems; + List items = getOpenItem().getItems(); for (PieItem item : items) { if (inside(polar, item)) { return item; @@ -656,20 +779,6 @@ public class PieRenderer extends OverlayRenderer return (int)(-60 + 120 * Math.random()); } - @Override - public void layout(int l, int t, int r, int b) { - super.layout(l, t, r, b); - mCenterX = (r - l) / 2; - mCenterY = (b - t) / 2; - mFocusX = mCenterX; - mFocusY = mCenterY; - setCircle(mFocusX, mFocusY); - if (isVisible() && mState == STATE_PIE) { - setCenter(mCenterX, mCenterY); - layoutPie(); - } - } - private void setCircle(int cx, int cy) { mCircle.set(cx - mCircleSize, cy - mCircleSize, cx + mCircleSize, cy + mCircleSize); @@ -849,7 +958,6 @@ public class PieRenderer extends OverlayRenderer } } - private class LinearAnimation extends Animation { private float mFrom; private float mTo; -- cgit v1.2.3