diff options
Diffstat (limited to 'src/com/android/gallery3d/ui')
-rw-r--r-- | src/com/android/gallery3d/ui/GLRoot.java | 1 | ||||
-rw-r--r-- | src/com/android/gallery3d/ui/GLRootView.java | 86 | ||||
-rw-r--r-- | src/com/android/gallery3d/ui/GLView.java | 10 | ||||
-rw-r--r-- | src/com/android/gallery3d/ui/PhotoView.java | 267 | ||||
-rw-r--r-- | src/com/android/gallery3d/ui/PositionController.java | 285 | ||||
-rw-r--r-- | src/com/android/gallery3d/ui/ScreenNailHolder.java | 31 | ||||
-rw-r--r-- | src/com/android/gallery3d/ui/TileImageView.java | 4 |
7 files changed, 486 insertions, 198 deletions
diff --git a/src/com/android/gallery3d/ui/GLRoot.java b/src/com/android/gallery3d/ui/GLRoot.java index fe040ba34..753f73c4b 100644 --- a/src/com/android/gallery3d/ui/GLRoot.java +++ b/src/com/android/gallery3d/ui/GLRoot.java @@ -36,4 +36,5 @@ public interface GLRoot { public void unlockRenderThread(); public void setContentPane(GLView content); + public void setOrientationCompensation(int degrees); } diff --git a/src/com/android/gallery3d/ui/GLRootView.java b/src/com/android/gallery3d/ui/GLRootView.java index f98f9a829..f063b84dd 100644 --- a/src/com/android/gallery3d/ui/GLRootView.java +++ b/src/com/android/gallery3d/ui/GLRootView.java @@ -17,6 +17,7 @@ package com.android.gallery3d.ui; import android.content.Context; +import android.graphics.Matrix; import android.graphics.PixelFormat; import android.graphics.Rect; import android.opengl.GLSurfaceView; @@ -67,9 +68,17 @@ public class GLRootView extends GLSurfaceView private GL11 mGL; private GLCanvas mCanvas; - private GLView mContentView; + // mCompensation is the difference between the UI orientation on GLCanvas + // and the framework orientation. See OrientationManager for details. + private int mCompensation; + // mCompensationMatrix maps the coordinates of touch events. It is kept sync + // with mCompensation. + private Matrix mCompensationMatrix = new Matrix(); + // The value which will become mCompensation in next layout. + private int mPendingCompensation; + private int mFlags = FLAG_NEED_LAYOUT; private volatile boolean mRenderRequested = false; @@ -175,11 +184,43 @@ public class GLRootView extends GLSurfaceView private void layoutContentPane() { mFlags &= ~FLAG_NEED_LAYOUT; - int width = getWidth(); - int height = getHeight(); - Log.i(TAG, "layout content pane " + width + "x" + height); - if (mContentView != null && width != 0 && height != 0) { - mContentView.layout(0, 0, width, height); + + int w = getWidth(); + int h = getHeight(); + + // Before doing layout, if there is a compensation change pending, update + // mCompensation and mCompensationMatrix. + if (mCompensation != mPendingCompensation) { + mCompensation = mPendingCompensation; + if (mCompensation % 180 != 0) { + mCompensationMatrix.setRotate(mCompensation); + // move center to origin before rotation + mCompensationMatrix.preTranslate(-w / 2, -h / 2); + // align with the new origin after rotation + mCompensationMatrix.postTranslate(h / 2, w / 2); + } else { + mCompensationMatrix.setRotate(mCompensation, w / 2, h / 2); + } + } + + // Tell the views about current display rotation and compensation value. + if (mContentView != null) { + // This is a hack: note the 0 should be the display rotation, but we + // don't know the display rotation here. The PhotoPage will inject + // the correct value in its mRootPane.orient() method. + mContentView.orient(0, mCompensation); + } + + // Do the actual layout. + if (mCompensation % 180 != 0) { + int tmp = w; + w = h; + h = tmp; + } + Log.i(TAG, "layout content pane " + w + "x" + h + + " (compensation " + mCompensation + ")"); + if (mContentView != null && w != 0 && h != 0) { + mContentView.layout(0, 0, w, h); } // Uncomment this to dump the view hierarchy. //mContentView.dumpTree(""); @@ -290,9 +331,12 @@ public class GLRootView extends GLSurfaceView if ((mFlags & FLAG_NEED_LAYOUT) != 0) layoutContentPane(); + mCanvas.save(GLCanvas.SAVE_FLAG_ALL); + rotateCanvas(-mCompensation); if (mContentView != null) { mContentView.render(mCanvas); } + mCanvas.restore(); if (!mAnimations.isEmpty()) { long now = AnimationTime.get(); @@ -320,9 +364,25 @@ public class GLRootView extends GLSurfaceView } } + private void rotateCanvas(int degrees) { + if (degrees == 0) return; + int w = getWidth(); + int h = getHeight(); + int cx = w / 2; + int cy = h / 2; + mCanvas.translate(cx, cy); + mCanvas.rotate(degrees, 0, 0, 1); + if (degrees % 180 != 0) { + mCanvas.translate(-cy, -cx); + } else { + mCanvas.translate(-cx, -cy); + } + } + @Override public boolean dispatchTouchEvent(MotionEvent event) { - AnimationTime.update(); + if (!isEnabled()) return false; + int action = event.getAction(); if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { @@ -330,6 +390,11 @@ public class GLRootView extends GLSurfaceView } else if (!mInDownState && action != MotionEvent.ACTION_DOWN) { return false; } + + if (mCompensation != 0) { + event.transform(mCompensationMatrix); + } + mRenderLock.lock(); try { // If this has been detached from root, we don't need to handle event @@ -396,4 +461,11 @@ public class GLRootView extends GLSurfaceView Profile.reset(); } } + + @Override + public void setOrientationCompensation(int degrees) { + if (mPendingCompensation == degrees) return; + mPendingCompensation = degrees; + requestLayoutContentPane(); + } } diff --git a/src/com/android/gallery3d/ui/GLView.java b/src/com/android/gallery3d/ui/GLView.java index 45471f910..ec29939e7 100644 --- a/src/com/android/gallery3d/ui/GLView.java +++ b/src/com/android/gallery3d/ui/GLView.java @@ -359,6 +359,16 @@ public class GLView { boolean changeSize, int left, int top, int right, int bottom) { } + protected void orient(int displayRotation, int compensation) { + onOrient(displayRotation, compensation); + for (int i = 0, n = getComponentCount(); i < n; ++i) { + getComponent(i).orient(displayRotation, compensation); + } + } + + protected void onOrient(int displayRotation, int compensation) { + } + /** * Gets the bounds of the given descendant that relative to this view. */ diff --git a/src/com/android/gallery3d/ui/PhotoView.java b/src/com/android/gallery3d/ui/PhotoView.java index c76996189..7f9cd043d 100644 --- a/src/com/android/gallery3d/ui/PhotoView.java +++ b/src/com/android/gallery3d/ui/PhotoView.java @@ -47,9 +47,8 @@ public class PhotoView extends GLView { } public interface Model extends TileImageView.Model { - public void next(); - public void previous(); - public void moveToFirst(); + public int getCurrentIndex(); + public void moveTo(int index); // Returns the size for the specified picture. If the size information is // not avaiable, width = height = 0. @@ -65,12 +64,42 @@ public class PhotoView extends GLView { // Set this to true if we need the model to provide full images. public void setNeedFullImage(boolean enabled); + + // Returns true if the item is the Camera preview. + public boolean isCamera(int offset); } - public interface PhotoTapListener { + public interface Listener { public void onSingleTapUp(int x, int y); + public void lockOrientation(); + public void unlockOrientation(); + public void onFullScreenChanged(boolean full); } + // Here is a graph showing the places we need to lock/unlock device + // orientation: + // + // +------------+ A +------------+ + // Page mode | Camera |<---| Photo | + // | [locked] |--->| [unlocked] | + // +------------+ B +------------+ + // ^ ^ + // | C | D + // +------------+ +------------+ + // | Camera | | Photo | + // Film mode | [*] | | [*] | + // +------------+ +------------+ + // + // In Page mode, we want to lock in Camera because we don't want the system + // rotation animation. We also want to unlock in Photo because we want to + // show the system action bar in the right place. + // + // We don't show action bar in Film mode, so it's fine for it to be locked + // or unlocked in Film mode. + // + // There are four transitions we need to check if we need to + // lock/unlock. Marked as A to D above and in the code. + private static final int MSG_SHOW_LOADING = 1; private static final int MSG_CANCEL_EXTRA_SCALING = 2; private static final int MSG_SWITCH_FOCUS = 3; @@ -111,11 +140,9 @@ public class PhotoView extends GLView { private final int mFromIndex[] = new int[2 * SCREEN_NAIL_MAX + 1]; private final GestureRecognizer mGestureRecognizer; - - private PhotoTapListener mPhotoTapListener; - private final PositionController mPositionController; + private Listener mListener; private Model mModel; private StringTexture mLoadingText; private StringTexture mNoThumbnailText; @@ -133,6 +160,11 @@ public class PhotoView extends GLView { private Point mImageCenter = new Point(); private boolean mCancelExtraScalingPending; private boolean mFilmMode = false; + private int mDisplayRotation = 0; + private int mCompensation = 0; + private boolean mFullScreen = true; + private Rect mCameraNaturalFrame = new Rect(); + private Rect mCameraRect = new Rect(); // [mPrevBound, mNextBound] is the range of index for all pictures in the // model, if we assume the index of current focused picture is 0. So if @@ -240,7 +272,9 @@ public class PhotoView extends GLView { break; } case MSG_CAPTURE_ANIMATION_DONE: { - captureAnimationDone(); + // message.arg1 is the offset parameter passed to + // switchWithCaptureAnimation(). + captureAnimationDone(message.arg1); break; } default: throw new AssertionError(message.what); @@ -314,7 +348,8 @@ public class PhotoView extends GLView { } // Move the boxes - mPositionController.moveBox(mFromIndex, mPrevBound < 0, mNextBound > 0); + mPositionController.moveBox(mFromIndex, mPrevBound < 0, mNextBound > 0, + mModel.isCamera(0)); // Update the ScreenNails. for (int i = -SCREEN_NAIL_MAX; i <= SCREEN_NAIL_MAX; i++) { @@ -329,6 +364,69 @@ public class PhotoView extends GLView { invalidate(); } + @Override + protected void onOrient(int displayRotation, int compensation) { + // onLayout will be called soon. We need to change the size and rotation + // of the Camera ScreenNail, but we don't want it start moving because + // the view size will be changed soon. + mDisplayRotation = displayRotation; + mCompensation = compensation; + for (int i = -SCREEN_NAIL_MAX; i <= SCREEN_NAIL_MAX; i++) { + Picture p = mPictures.get(i); + if (p.isCamera()) { + p.updateSize(true); + } + } + } + + @Override + protected void onLayout( + boolean changeSize, int left, int top, int right, int bottom) { + mTileView.layout(left, top, right, bottom); + mEdgeView.layout(left, top, right, bottom); + updateConstrainedFrame(); + if (changeSize) { + mPositionController.setViewSize(getWidth(), getHeight()); + } + } + + // Update the constrained frame due to layout change. + private void updateConstrainedFrame() { + int w = getWidth(); + int h = getHeight(); + int rotation = getCameraRotation(); + if (rotation % 180 != 0) { + int tmp = w; + w = h; + h = tmp; + } + + int l = mCameraNaturalFrame.left; + int t = mCameraNaturalFrame.top; + int r = mCameraNaturalFrame.right; + int b = mCameraNaturalFrame.bottom; + + switch (rotation) { + case 0: mCameraRect.set(l, t, r, b); break; + case 90: mCameraRect.set(h - b, l, h - t, r); break; + case 180: mCameraRect.set(w - r, h - b, w - l, h - t); break; + case 270: mCameraRect.set(t, w - r, b, w - l); break; + } + + mPositionController.setConstrainedFrame(mCameraRect); + } + + public void setCameraNaturalFrame(Rect frame) { + mCameraNaturalFrame.set(frame); + } + + // Returns the rotation we need to do to the camera texture before drawing + // it to the canvas, assuming the camera texture is correct when the device + // is in its natural orientation. + private int getCameraRotation() { + return (mCompensation - mDisplayRotation + 360) % 360; + } + //////////////////////////////////////////////////////////////////////////// // Pictures //////////////////////////////////////////////////////////////////////////// @@ -338,16 +436,13 @@ public class PhotoView extends GLView { void draw(GLCanvas canvas, Rect r); void setScreenNail(ScreenNail s); boolean isCamera(); // whether the picture is a camera preview + void updateSize(boolean force); // called when mCompensation changes }; - private boolean isCameraScreenNail(ScreenNail s) { - return s != null && !(s instanceof BitmapScreenNail); - } - class FullPicture implements Picture { private int mRotation; private boolean mIsCamera; - private boolean mWasCenter; + private boolean mWasCameraCenter; public void FullPicture(TileImageView tileView) { mTileView = tileView; @@ -359,45 +454,73 @@ public class PhotoView extends GLView { mTileView.notifyModelInvalidated(); mTileView.setAlpha(1.0f); - mRotation = mModel.getImageRotation(0); + mIsCamera = mModel.isCamera(0); + setScreenNail(mModel.getScreenNail(0)); + updateSize(false); + updateLoadingState(); + } + + @Override + public void updateSize(boolean force) { + if (mIsCamera) { + mRotation = getCameraRotation(); + } else { + mRotation = mModel.getImageRotation(0); + } + int w = mTileView.mImageWidth; int h = mTileView.mImageHeight; mPositionController.setImageSize(0, getRotated(mRotation, w, h), - getRotated(mRotation, h, w)); - - setScreenNail(mModel.getScreenNail(0)); - updateLoadingState(); + getRotated(mRotation, h, w), + force); } @Override public void draw(GLCanvas canvas, Rect r) { + boolean isCenter = mPositionController.isCenter(); + if (mLoadingState == LOADING_COMPLETE) { + if (mIsCamera) { + boolean full = !mFilmMode && isCenter + && mPositionController.isAtMinimalScale(); + if (full != mFullScreen) { + mFullScreen = full; + mListener.onFullScreenChanged(full); + } + } setTileViewPosition(r); PhotoView.super.render(canvas); } renderMessage(canvas, r.centerX(), r.centerY()); - boolean isCenter = r.centerX() == getWidth() / 2; - - // We want to have following transitions: + // We want to have the following transitions: // (1) Move camera preview out of its place: switch to film mode // (2) Move camera preview into its place: switch to page mode // The extra mWasCenter check makes sure (1) does not apply if in // page mode, we move _to_ the camera preview from another picture. - if ((mHolding & ~(HOLD_TOUCH_DOWN | HOLD_TOUCH_DOWN_FROM_CAMERA)) == 0) { - if (mWasCenter && !isCenter && mIsCamera && !mFilmMode) { - setFilmMode(true); - } else if (mIsCamera && isCenter && mFilmMode) { - setFilmMode(false); - } + + // Holdings except touch-down prevent the transitions. + if ((mHolding & ~(HOLD_TOUCH_DOWN | HOLD_TOUCH_DOWN_FROM_CAMERA)) != 0) { + return; } - mWasCenter = isCenter; + + boolean isCameraCenter = mIsCamera && isCenter; + + if (mWasCameraCenter && mIsCamera && !isCenter && !mFilmMode) { + setFilmMode(true); + } else if (isCameraCenter && mFilmMode) { + setFilmMode(false); + } else if (isCameraCenter && !mFilmMode) { + // move into camera, lock + mListener.lockOrientation(); // Transition A + } + + mWasCameraCenter = isCameraCenter; } @Override public void setScreenNail(ScreenNail s) { - mIsCamera = isCameraScreenNail(s); mTileView.setScreenNail(s); } @@ -421,7 +544,7 @@ public class PhotoView extends GLView { (viewH / 2f - r.exactCenterY()) / scale + 0.5f); boolean wantsCardEffect = CARD_EFFECT && !mFilmMode - && !mPictures.get(-1).isCamera() && !mIsCamera; + && !mIsCamera && !mPictures.get(-1).isCamera(); if (wantsCardEffect) { // Calculate the move-out progress value. int left = r.left; @@ -431,7 +554,7 @@ public class PhotoView extends GLView { // We only want to apply the fading animation if the scrolling // movement is to the right. - if (progress <= 0) { + if (progress < 0) { if (right - left < viewW) { // If the picture is narrower than the view, keep it at // the center of the view. @@ -509,6 +632,7 @@ public class PhotoView extends GLView { @Override public void reload() { + mIsCamera = mModel.isCamera(mIndex); setScreenNail(mModel.getScreenNail(mIndex)); } @@ -529,6 +653,11 @@ public class PhotoView extends GLView { return; } + if (mIsCamera && mFullScreen != false) { + mFullScreen = false; + mListener.onFullScreenChanged(false); + } + boolean wantsCardEffect = CARD_EFFECT && !mFilmMode && (mIndex > 0) && !mPictures.get(0).isCamera(); @@ -561,13 +690,21 @@ public class PhotoView extends GLView { public void setScreenNail(ScreenNail s) { if (mScreenNail == s) return; mScreenNail = s; - mIsCamera = isCameraScreenNail(s); - mRotation = mModel.getImageRotation(mIndex); + updateSize(false); + } + + @Override + public void updateSize(boolean force) { + if (mIsCamera) { + mRotation = getCameraRotation(); + } else { + mRotation = mModel.getImageRotation(mIndex); + } int w = 0, h = 0; if (mScreenNail != null) { - w = s.getWidth(); - h = s.getHeight(); + w = mScreenNail.getWidth(); + h = mScreenNail.getHeight(); } else if (mModel != null) { // If we don't have ScreenNail available, we can still try to // get the size information of it. @@ -579,7 +716,8 @@ public class PhotoView extends GLView { if (w != 0 && h != 0) { mPositionController.setImageSize(mIndex, getRotated(mRotation, w, h), - getRotated(mRotation, h, w)); + getRotated(mRotation, h, w), + force); } } @@ -617,8 +755,8 @@ public class PhotoView extends GLView { return true; } - if (mPhotoTapListener != null) { - mPhotoTapListener.onSingleTapUp((int) x, (int) y); + if (mListener != null) { + mListener.onSingleTapUp((int) x, (int) y); } return true; } @@ -737,22 +875,25 @@ public class PhotoView extends GLView { mFilmMode = enabled; mPositionController.setFilmMode(mFilmMode); mModel.setNeedFullImage(!enabled); + + // If we leave filmstrip mode, we should lock/unlock + if (!enabled) { + if (mPictures.get(0).isCamera()) { + mListener.lockOrientation(); // Transition C + } else { + mListener.unlockOrientation(); // Transition D + } + } + } + + public boolean getFilmMode() { + return mFilmMode; } //////////////////////////////////////////////////////////////////////////// // Framework events //////////////////////////////////////////////////////////////////////////// - @Override - protected void onLayout( - boolean changeSize, int left, int top, int right, int bottom) { - mTileView.layout(left, top, right, bottom); - mEdgeView.layout(left, top, right, bottom); - if (changeSize) { - mPositionController.setViewSize(getWidth(), getHeight()); - } - } - public void pause() { mPositionController.skipAnimation(); mTileView.freeTextures(); @@ -926,15 +1067,15 @@ public class PhotoView extends GLView { //////////////////////////////////////////////////////////////////////////// private void switchToNextImage() { - mModel.next(); + mModel.moveTo(mModel.getCurrentIndex() + 1); } private void switchToPrevImage() { - mModel.previous(); + mModel.moveTo(mModel.getCurrentIndex() - 1); } private void switchToFirstImage() { - mModel.moveToFirst(); + mModel.moveTo(0); } //////////////////////////////////////////////////////////////////////////// @@ -973,12 +1114,17 @@ public class PhotoView extends GLView { return false; } mHolding |= HOLD_CAPTURE_ANIMATION; - mHandler.sendEmptyMessageDelayed(MSG_CAPTURE_ANIMATION_DONE, 800); + Message m = mHandler.obtainMessage(MSG_CAPTURE_ANIMATION_DONE, offset, 0); + mHandler.sendMessageDelayed(m, 800); return true; } - private void captureAnimationDone() { + private void captureAnimationDone(int offset) { mHolding &= ~HOLD_CAPTURE_ANIMATION; + if (offset == 1) { + // move out of camera, unlock + if (!mFilmMode) mListener.unlockOrientation(); // Transition B + } snapback(); } @@ -1004,10 +1150,15 @@ public class PhotoView extends GLView { // If the object width is smaller than the view width, // |....view....| // |<-->| progress = -1 when left = viewWidth + // |<-->| progress = 0 when left = viewWidth / 2 - w / 2 // |<-->| progress = 1 when left = -w - // So progress = 1 - 2 * (left + w) / (viewWidth + w) if (w < viewWidth) { - return 1f - 2f * (left + w) / (viewWidth + w); + int zx = viewWidth / 2 - w / 2; + if (left > zx) { + return -(left - zx) / (float) (viewWidth - zx); // progress = (0, -1] + } else { + return (left - zx) / (float) (-w - zx); // progress = [0, 1] + } } // If the object width is larger than the view width, @@ -1066,8 +1217,8 @@ public class PhotoView extends GLView { // Simple public utilities //////////////////////////////////////////////////////////////////////////// - public void setPhotoTapListener(PhotoTapListener listener) { - mPhotoTapListener = listener; + public void setListener(Listener listener) { + mListener = listener; } public void showVideoPlayIcon(boolean show) { diff --git a/src/com/android/gallery3d/ui/PositionController.java b/src/com/android/gallery3d/ui/PositionController.java index e6c132b85..ac0d191f4 100644 --- a/src/com/android/gallery3d/ui/PositionController.java +++ b/src/com/android/gallery3d/ui/PositionController.java @@ -112,6 +112,26 @@ class PositionController { // comments above calculateStableBound() for details. private int mBoundLeft, mBoundRight, mBoundTop, mBoundBottom; + // Constrained frame is a rectangle that the focused box should fit into if + // it is constrained. It has two effects: + // + // (1) In page mode, if the focused box is constrained, scaling for the + // focused box is adjusted to fit into the constrained frame, instead of the + // whole view. + // + // (2) In page mode, if the focused box is constrained, the mPlatform's + // default center (mDefaultX/Y) is moved to the center of the constrained + // frame, instead of the view center. + // + private Rect mConstrainedFrame = new Rect(); + + // Whether the focused box is constrained. + // + // Our current program's first call to moveBox() sets constrained = true, so + // we set the initial value of this variable to true, and we will not see + // see unwanted transition animation. + private boolean mConstrained = true; + // // ___________________________________________________________ // | _____ _____ _____ _____ _____ | @@ -123,7 +143,7 @@ class PositionController { // // <-- Platform --> // - // The focused box (Box*) centers at mPlatform.mCurrentX + // The focused box (Box*) centers at mPlatform's (mCurrentX, mCurrentY) private Platform mPlatform = new Platform(); private RangeArray<Box> mBoxes = new RangeArray<Box>(-BOX_MAX, BOX_MAX); @@ -152,7 +172,8 @@ class PositionController { public PositionController(Context context, Listener listener) { mListener = listener; mPageScroller = new FlingScroller(); - mFilmScroller = new OverScroller(context); + mFilmScroller = new OverScroller(context, + null /* default interpolator */, false /* no flywheel */); // Initialize the areas. initPlatform(); @@ -186,7 +207,22 @@ class PositionController { snapAndRedraw(); } - public void setImageSize(int index, int width, int height) { + public void setConstrainedFrame(Rect f) { + if (mConstrainedFrame.equals(f)) return; + mConstrainedFrame.set(f); + mPlatform.updateDefaultXY(); + updateScaleAndGapLimit(); + snapAndRedraw(); + } + + public void setImageSize(int index, int width, int height, boolean force) { + if (force) { + Box b = mBoxes.get(index); + b.mImageW = width; + b.mImageH = height; + return; + } + if (width == 0 || height == 0) { initBox(index); } else if (!setBoxSize(index, width, height, false)) { @@ -216,11 +252,14 @@ class PositionController { float ratio = Math.min( (float) b.mImageW / width, (float) b.mImageH / height); + b.mImageW = width; + b.mImageH = height; + // If this is the first time we receive an image size, we change the // scale directly. Otherwise adjust the scales by a ratio, and snapback // will animate the scale into the min/max bounds if necessary. if (wasViewSize && !isViewSize) { - b.mCurrentScale = getMinimalScale(width, height); + b.mCurrentScale = getMinimalScale(b); b.mAnimationStartTime = NO_ANIMATION; } else { b.mCurrentScale *= ratio; @@ -228,9 +267,6 @@ class PositionController { b.mToScale *= ratio; } - b.mImageW = width; - b.mImageH = height; - if (i == 0) { mFocusX /= ratio; mFocusY /= ratio; @@ -247,17 +283,19 @@ class PositionController { // Start animation from the saved rectangle if we have one. Rect r = mOpenAnimationRect; mOpenAnimationRect = null; - mPlatform.mCurrentX = r.centerX(); - b.mCurrentY = r.centerY(); + mPlatform.mCurrentX = r.centerX() - mViewW / 2; + b.mCurrentY = r.centerY() - mViewH / 2; b.mCurrentScale = Math.max(r.width() / (float) b.mImageW, r.height() / (float) b.mImageH); - startAnimation(mViewW / 2, mViewH / 2, b.mScaleMin, ANIM_KIND_OPENING); + startAnimation(mPlatform.mDefaultX, 0, b.mScaleMin, + ANIM_KIND_OPENING); } public void setFilmMode(boolean enabled) { if (enabled == mFilmMode) return; mFilmMode = enabled; + mPlatform.updateDefaultXY(); updateScaleAndGapLimit(); stopAnimation(); snapAndRedraw(); @@ -273,12 +311,12 @@ class PositionController { // This should be called whenever the scale range of boxes or the default // gap size may change. Currently this can happen due to change of view - // size, image size, and mode. + // size, image size, mFilmMode, mConstrained, and mConstrainedFrame. private void updateScaleAndGapLimit() { for (int i = -BOX_MAX; i <= BOX_MAX; i++) { Box b = mBoxes.get(i); - b.mScaleMin = getMinimalScale(b.mImageW, b.mImageH); - b.mScaleMax = getMaximalScale(b.mImageW, b.mImageH); + b.mScaleMin = getMinimalScale(b); + b.mScaleMax = getMaximalScale(b); } for (int i = -BOX_MAX; i < BOX_MAX; i++) { @@ -326,6 +364,7 @@ class PositionController { public void skipAnimation() { if (mPlatform.mAnimationStartTime != NO_ANIMATION) { mPlatform.mCurrentX = mPlatform.mToX; + mPlatform.mCurrentY = mPlatform.mToY; mPlatform.mAnimationStartTime = NO_ANIMATION; } for (int i = -BOX_MAX; i <= BOX_MAX; i++) { @@ -353,14 +392,16 @@ class PositionController { //////////////////////////////////////////////////////////////////////////// public void zoomIn(float tapX, float tapY, float targetScale) { + tapX -= mViewW / 2; + tapY -= mViewH / 2; Box b = mBoxes.get(0); // Convert the tap position to distance to center in bitmap coordinates float tempX = (tapX - mPlatform.mCurrentX) / b.mCurrentScale; float tempY = (tapY - b.mCurrentY) / b.mCurrentScale; - int x = (int) (mViewW / 2 - tempX * targetScale + 0.5f); - int y = (int) (mViewH / 2 - tempY * targetScale + 0.5f); + int x = (int) (-tempX * targetScale + 0.5f); + int y = (int) (-tempY * targetScale + 0.5f); calculateStableBound(targetScale); int targetX = Utils.clamp(x, mBoundLeft, mBoundRight); @@ -372,10 +413,12 @@ class PositionController { public void resetToFullView() { Box b = mBoxes.get(0); - startAnimation(mViewW / 2, mViewH / 2, b.mScaleMin, ANIM_KIND_ZOOM); + startAnimation(mPlatform.mDefaultX, 0, b.mScaleMin, ANIM_KIND_ZOOM); } public void beginScale(float focusX, float focusY) { + focusX -= mViewW / 2; + focusY -= mViewH / 2; Box b = mBoxes.get(0); Platform p = mPlatform; mInScale = true; @@ -389,6 +432,8 @@ class PositionController { // 0 if the intended scale is in the stable range. // -1 if the intended scale is too small for the stable range. public int scaleBy(float s, float focusX, float focusY) { + focusX -= mViewW / 2; + focusY -= mViewH / 2; Box b = mBoxes.get(0); Platform p = mPlatform; @@ -414,7 +459,7 @@ class PositionController { // Slide the focused box to the center of the view. public void startHorizontalSlide() { Box b = mBoxes.get(0); - startAnimation(mViewW / 2, mViewH / 2, b.mScaleMin, ANIM_KIND_SLIDE); + startAnimation(mPlatform.mDefaultX, 0, b.mScaleMin, ANIM_KIND_SLIDE); } // Slide the focused box to the center of the view with the capture @@ -426,9 +471,10 @@ class PositionController { Box n = mBoxes.get(offset); // the neighbor box Gap g = mGaps.get(offset); // the gap between the two boxes - mPlatform.doAnimation(mViewW / 2, ANIM_KIND_CAPTURE); - b.doAnimation(mViewH / 2, b.mScaleMin, ANIM_KIND_CAPTURE); - n.doAnimation(mViewH / 2, n.mScaleMin, ANIM_KIND_CAPTURE); + mPlatform.doAnimation(mPlatform.mDefaultX, mPlatform.mDefaultY, + ANIM_KIND_CAPTURE); + b.doAnimation(0, b.mScaleMin, ANIM_KIND_CAPTURE); + n.doAnimation(0, n.mScaleMin, ANIM_KIND_CAPTURE); g.doAnimation(g.mDefaultSize, ANIM_KIND_CAPTURE); redraw(); } @@ -484,17 +530,15 @@ class PositionController { // Horizontal direction: we show the edge effect when the scrolling // tries to go left of the first image or go right of the last image. - int cx = mViewW / 2; - if (!mHasPrev && x > cx) { - int pixels = x - cx; - mListener.onPull(pixels, EdgeView.LEFT); - x = cx; - } else if (!mHasNext && x < cx) { - int pixels = cx - x; - mListener.onPull(pixels, EdgeView.RIGHT); - x = cx; - } - + x -= mPlatform.mDefaultX; + if (!mHasPrev && x > 0) { + mListener.onPull(x, EdgeView.LEFT); + x = 0; + } else if (!mHasNext && x < 0) { + mListener.onPull(-x, EdgeView.RIGHT); + x = 0; + } + x += mPlatform.mDefaultX; startAnimation(x, y, b.mCurrentScale, ANIM_KIND_SCROLL); } @@ -542,9 +586,9 @@ class PositionController { Platform p = mPlatform; // If we are already at the edge, don't start the fling. - int cx = mViewW / 2; - if ((!mHasPrev && p.mCurrentX >= cx) - || (!mHasNext && p.mCurrentX <= cx)) { + int defaultX = p.mDefaultX; + if ((!mHasPrev && p.mCurrentX >= defaultX) + || (!mHasNext && p.mCurrentX <= defaultX)) { return false; } @@ -595,7 +639,7 @@ class PositionController { private void startAnimation(int targetX, int targetY, float targetScale, int kind) { boolean changed = false; - changed |= mPlatform.doAnimation(targetX, kind); + changed |= mPlatform.doAnimation(targetX, mPlatform.mDefaultY, kind); changed |= mBoxes.get(0).doAnimation(targetY, targetScale, kind); if (changed) redraw(); } @@ -691,11 +735,11 @@ class PositionController { private void convertBoxToRect(int i) { Box b = mBoxes.get(i); Rect r = mRects.get(i); - int y = b.mCurrentY; + int y = b.mCurrentY + mPlatform.mCurrentY + mViewH / 2; int w = widthOf(b); int h = heightOf(b); if (i == 0) { - int x = mPlatform.mCurrentX; + int x = mPlatform.mCurrentX + mViewW / 2; r.left = x - w / 2; r.right = r.left + w; } else if (i > 0) { @@ -724,7 +768,9 @@ class PositionController { // Initialize the platform to be at the view center. private void initPlatform() { - mPlatform.mCurrentX = mViewW / 2; + mPlatform.updateDefaultXY(); + mPlatform.mCurrentX = mPlatform.mDefaultX; + mPlatform.mCurrentY = mPlatform.mDefaultY; mPlatform.mAnimationStartTime = NO_ANIMATION; } @@ -734,9 +780,9 @@ class PositionController { b.mImageW = mViewW; b.mImageH = mViewH; b.mUseViewSize = true; - b.mScaleMin = getMinimalScale(b.mImageW, b.mImageH); - b.mScaleMax = getMaximalScale(b.mImageW, b.mImageH); - b.mCurrentY = mViewH / 2; + b.mScaleMin = getMinimalScale(b); + b.mScaleMax = getMaximalScale(b); + b.mCurrentY = 0; b.mCurrentScale = b.mScaleMin; b.mAnimationStartTime = NO_ANIMATION; } @@ -784,10 +830,16 @@ class PositionController { // -2 -1 0 1 2 3 N -- focus goes to the next box // N -3 -2 -1 0 1 2 -- focuse goes to the previous box // -3 -2 -1 1 2 3 N -- the focused box was deleted. - public void moveBox(int fromIndex[], boolean hasPrev, boolean hasNext) { + // + // hasPrev/hasNext indicates if there are previous/next boxes for the + // focused box. constrained indicates whether the focused box should be put + // into the constrained frame. + public void moveBox(int fromIndex[], boolean hasPrev, boolean hasNext, + boolean constrained) { //debugMoveBox(fromIndex); mHasPrev = hasPrev; mHasNext = hasNext; + RangeIntArray from = new RangeIntArray(fromIndex, -BOX_MAX, BOX_MAX); // 1. Get the absolute X coordiates for the boxes. @@ -795,7 +847,7 @@ class PositionController { for (int i = -BOX_MAX; i <= BOX_MAX; i++) { Box b = mBoxes.get(i); Rect r = mRects.get(i); - b.mAbsoluteX = r.centerX(); + b.mAbsoluteX = r.centerX() - mViewW / 2; } // 2. copy boxes and gaps to temporary storage. @@ -892,6 +944,12 @@ class PositionController { mPlatform.mToX += dx; mPlatform.mFlingOffset += dx; + if (mConstrained != constrained) { + mConstrained = constrained; + mPlatform.updateDefaultXY(); + updateScaleAndGapLimit(); + } + snapAndRedraw(); } @@ -899,34 +957,17 @@ class PositionController { // Public utilities //////////////////////////////////////////////////////////////////////////// - public float getMinimalScale(int imageW, int imageH) { - float wFactor = 1.0f; - float hFactor = 1.0f; - - if (mFilmMode) { - if (mViewH > mViewW) { // portrait - wFactor = FILM_MODE_PORTRAIT_WIDTH; - hFactor = FILM_MODE_PORTRAIT_HEIGHT; - } else { // landscape - wFactor = FILM_MODE_LANDSCAPE_WIDTH; - hFactor = FILM_MODE_LANDSCAPE_HEIGHT; - } - } - - float s = Math.min(wFactor * mViewW / imageW, - hFactor * mViewH / imageH); - return Math.min(SCALE_LIMIT, s); - } - - public float getMaximalScale(int imageW, int imageH) { - return mFilmMode ? getMinimalScale(imageW, imageH) : SCALE_LIMIT; - } - public boolean isAtMinimalScale() { Box b = mBoxes.get(0); return isAlmostEqual(b.mCurrentScale, b.mScaleMin); } + public boolean isCenter() { + Box b = mBoxes.get(0); + return mPlatform.mCurrentX == mPlatform.mDefaultX + && b.mCurrentY == 0; + } + public int getImageWidth() { Box b = mBoxes.get(0); return b.mImageW; @@ -967,11 +1008,35 @@ class PositionController { //////////////////////////////////////////////////////////////////////////// private float getMinimalScale(Box b) { - return getMinimalScale(b.mImageW, b.mImageH); + float wFactor = 1.0f; + float hFactor = 1.0f; + int viewW, viewH; + + if (!mFilmMode && mConstrained && b == mBoxes.get(0)) { + viewW = mConstrainedFrame.width(); + viewH = mConstrainedFrame.height(); + } else { + viewW = mViewW; + viewH = mViewH; + } + + if (mFilmMode) { + if (mViewH > mViewW) { // portrait + wFactor = FILM_MODE_PORTRAIT_WIDTH; + hFactor = FILM_MODE_PORTRAIT_HEIGHT; + } else { // landscape + wFactor = FILM_MODE_LANDSCAPE_WIDTH; + hFactor = FILM_MODE_LANDSCAPE_HEIGHT; + } + } + + float s = Math.min(wFactor * viewW / b.mImageW, + hFactor * viewH / b.mImageH); + return Math.min(SCALE_LIMIT, s); } - private float getMaxmimalScale(Box b) { - return getMaximalScale(b.mImageW, b.mImageH); + private float getMaximalScale(Box b) { + return mFilmMode ? getMinimalScale(b) : SCALE_LIMIT; } private static boolean isAlmostEqual(float a, float b) { @@ -979,7 +1044,8 @@ class PositionController { return (diff < 0 ? -diff : diff) < 0.02f; } - // Calculates the stable region of mCurrent{X/Y}, where "stable" means + // Calculates the stable region of mPlatform.mCurrentX and + // mBoxes.get(0).mCurrentY, where "stable" means // // (1) If the dimension of scaled image >= view dimension, we will not // see black region outside the image (at that dimension). @@ -1002,20 +1068,20 @@ class PositionController { int h = heightOf(b, scale); // When the edge of the view is aligned with the edge of the box - mBoundLeft = (mViewW - horizontalSlack) - w / 2; - mBoundRight = mViewW - mBoundLeft; - mBoundTop = mViewH - h / 2; - mBoundBottom = mViewH - mBoundTop; + mBoundLeft = (mViewW + 1) / 2 - (w + 1) / 2 - horizontalSlack; + mBoundRight = w / 2 - mViewW / 2 + horizontalSlack; + mBoundTop = (mViewH + 1) / 2 - (h + 1) / 2; + mBoundBottom = h / 2 - mViewH / 2; // If the scaled height is smaller than the view height, // force it to be in the center. if (viewTallerThanScaledImage(scale)) { - mBoundTop = mBoundBottom = mViewH / 2; + mBoundTop = mBoundBottom = 0; } // Same for width if (viewWiderThanScaledImage(scale)) { - mBoundLeft = mBoundRight = mViewW / 2; + mBoundLeft = mBoundRight = mPlatform.mDefaultX; } } @@ -1049,13 +1115,6 @@ class PositionController { a.mAnimationKind == ANIM_KIND_FLING; } - // Returns the index of the anchor box. - private int anchorIndex(int i) { - if (i > 0) return i - 1; - if (i < 0) return i + 1; - throw new IllegalArgumentException(); - } - //////////////////////////////////////////////////////////////////////////// // Animatable: an thing which can do animation. //////////////////////////////////////////////////////////////////////////// @@ -1127,10 +1186,11 @@ class PositionController { } //////////////////////////////////////////////////////////////////////////// - // Platform: captures the global X movement. + // Platform: captures the global X/Y movement. //////////////////////////////////////////////////////////////////////////// private class Platform extends Animatable { - public int mCurrentX, mFromX, mToX; + public int mCurrentX, mFromX, mToX, mDefaultX; + public int mCurrentY, mFromY, mToY, mDefaultY; public int mFlingOffset; @Override @@ -1146,25 +1206,47 @@ class PositionController { b.mScaleMax * SCALE_MAX_EXTRA : b.mScaleMax; float scale = Utils.clamp(b.mCurrentScale, scaleMin, scaleMax); int x = mCurrentX; + int y = mDefaultY; if (mFilmMode) { - if (!mHasNext) x = Math.max(x, mViewW / 2); - if (!mHasPrev) x = Math.min(x, mViewW / 2); + int defaultX = mDefaultX; + if (!mHasNext) x = Math.max(x, defaultX); + if (!mHasPrev) x = Math.min(x, defaultX); } else { calculateStableBound(scale, HORIZONTAL_SLACK); x = Utils.clamp(x, mBoundLeft, mBoundRight); } - if (mCurrentX != x) { - return doAnimation(x, ANIM_KIND_SNAPBACK); + if (mCurrentX != x || mCurrentY != y) { + return doAnimation(x, y, ANIM_KIND_SNAPBACK); } return false; } + // The updateDefaultXY() should be called whenever these variables + // changes: (1) mConstrained (2) mConstrainedFrame (3) mViewW/H (4) + // mFilmMode + public void updateDefaultXY() { + // We don't check mFilmMode and return 0 for mDefaultX. Because + // otherwise if we decide to leave film mode because we are + // centered, we will immediately back into film mode because we find + // we are not centered. + if (mConstrained && !mConstrainedFrame.isEmpty()) { + mDefaultX = mConstrainedFrame.centerX() - mViewW / 2; + mDefaultY = mFilmMode ? 0 : + mConstrainedFrame.centerY() - mViewH / 2; + } else { + mDefaultX = 0; + mDefaultY = 0; + } + } + // Starts an animation for the platform. - public boolean doAnimation(int targetX, int kind) { - if (mCurrentX == targetX) return false; + private boolean doAnimation(int targetX, int targetY, int kind) { + if (mCurrentX == targetX && mCurrentY == targetY) return false; mAnimationKind = kind; mFromX = mCurrentX; + mFromY = mCurrentY; mToX = targetX; + mToY = targetY; mAnimationStartTime = AnimationTime.startTime(); mAnimationDuration = ANIM_TIME[kind]; mFlingOffset = 0; @@ -1188,11 +1270,11 @@ class PositionController { mCurrentX = mFilmScroller.getCurrX() + mFlingOffset; int dir = EdgeView.INVALID_DIRECTION; - if (mCurrentX < mViewW / 2) { + if (mCurrentX < mDefaultX) { if (!mHasNext) { dir = EdgeView.RIGHT; } - } else if (mCurrentX > mViewW / 2) { + } else if (mCurrentX > mDefaultX) { if (!mHasPrev) { dir = EdgeView.LEFT; } @@ -1201,7 +1283,7 @@ class PositionController { int v = (int) (mFilmScroller.getCurrVelocity() + 0.5f); mListener.onAbsorb(v, dir); mFilmScroller.forceFinished(true); - mCurrentX = mViewW / 2; + mCurrentX = mDefaultX; } return mFilmScroller.isFinished(); } @@ -1230,15 +1312,18 @@ class PositionController { // Other animations if (progress >= 1) { mCurrentX = mToX; + mCurrentY = mToY; return true; } else { if (mAnimationKind == ANIM_KIND_CAPTURE) { progress = CaptureAnimation.calculateSlide(progress); - mCurrentX = (int) (mFromX + progress * (mToX - mFromX)); + } + mCurrentX = (int) (mFromX + progress * (mToX - mFromX)); + mCurrentY = (int) (mFromY + progress * (mToY - mFromY)); + if (mAnimationKind == ANIM_KIND_CAPTURE) { return false; } else { - mCurrentX = (int) (mFromX + progress * (mToX - mFromX)); - return (mCurrentX == mToX); + return (mCurrentX == mToX && mCurrentY == mToY); } } } @@ -1287,13 +1372,13 @@ class PositionController { mScaleMax * SCALE_MAX_EXTRA : mScaleMax; scale = Utils.clamp(mCurrentScale, scaleMin, scaleMax); if (mFilmMode) { - y = mViewH / 2; + y = 0; } else { calculateStableBound(scale, HORIZONTAL_SLACK); y = Utils.clamp(mCurrentY, mBoundTop, mBoundBottom); } } else { - y = mViewH / 2; + y = 0; scale = mScaleMin; } @@ -1312,7 +1397,7 @@ class PositionController { // in the center. (We do this for height only, not width, because the // user may want to scroll to the previous/next image.) if (!mInScale && viewTallerThanScaledImage(targetScale)) { - targetY = mViewH / 2; + targetY = 0; } if (mCurrentY == targetY && mCurrentScale == targetScale diff --git a/src/com/android/gallery3d/ui/ScreenNailHolder.java b/src/com/android/gallery3d/ui/ScreenNailHolder.java deleted file mode 100644 index a7d541767..000000000 --- a/src/com/android/gallery3d/ui/ScreenNailHolder.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.gallery3d.ui; - -import android.os.Parcel; -import android.os.Parcelable; - -public abstract class ScreenNailHolder implements Parcelable { - public int describeContents() { - return 0; - } - - public void writeToParcel(Parcel dest, int flags) { - } - - public abstract ScreenNail attach(); - public abstract void detach(); -} diff --git a/src/com/android/gallery3d/ui/TileImageView.java b/src/com/android/gallery3d/ui/TileImageView.java index b37cf9c4a..eb5da891a 100644 --- a/src/com/android/gallery3d/ui/TileImageView.java +++ b/src/com/android/gallery3d/ui/TileImageView.java @@ -337,8 +337,8 @@ public class TileImageView extends GLView { } public boolean setPosition(int centerX, int centerY, float scale, int rotation) { - if (mCenterX == centerX - && mCenterY == centerY && mScale == scale) return false; + if (mCenterX == centerX && mCenterY == centerY + && mScale == scale && mRotation == rotation) return false; mCenterX = centerX; mCenterY = centerY; mScale = scale; |