blob: 5978159e4382d261be49c98dcf2e09294ab60e63 [file] [log] [blame]
Owen Linf9a0a432011-08-17 22:07:43 +08001/*
2 * Copyright (C) 2010 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.gallery3d.ui;
18
Owen Linf9a0a432011-08-17 22:07:43 +080019import android.content.Context;
Owen Linf9a0a432011-08-17 22:07:43 +080020import android.graphics.Color;
Chih-Chung Chang2ef46ed2012-05-08 17:57:33 +080021import android.graphics.Matrix;
Yuli Huang04ac0452012-03-20 16:37:05 +080022import android.graphics.Rect;
Hung-ying Tyan4d47b342012-08-29 14:15:32 +080023import android.os.Build;
Owen Linf9a0a432011-08-17 22:07:43 +080024import android.os.Message;
Chih-Chung Chang6b891c62012-06-07 20:09:13 +080025import android.util.FloatMath;
Owen Linf9a0a432011-08-17 22:07:43 +080026import android.view.MotionEvent;
Chih-Chung Chang6b891c62012-06-07 20:09:13 +080027import android.view.View.MeasureSpec;
Chih-Chung Changcb4fb7c2012-03-21 16:19:21 +080028import android.view.animation.AccelerateInterpolator;
Owen Linf9a0a432011-08-17 22:07:43 +080029
Owen Lin2b3ee0e2012-03-14 17:27:24 +080030import com.android.gallery3d.R;
Owen Linb21b8e52012-08-24 12:25:57 +080031import com.android.gallery3d.app.AbstractGalleryActivity;
Hung-ying Tyan4d47b342012-08-29 14:15:32 +080032import com.android.gallery3d.common.ApiHelper;
Chih-Chung Changcb4fb7c2012-03-21 16:19:21 +080033import com.android.gallery3d.common.Utils;
Owen Lin616a70f2012-05-07 16:35:53 +080034import com.android.gallery3d.data.MediaItem;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +080035import com.android.gallery3d.data.MediaObject;
Chih-Chung Chang6b891c62012-06-07 20:09:13 +080036import com.android.gallery3d.data.Path;
37import com.android.gallery3d.util.GalleryUtils;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +080038import com.android.gallery3d.util.RangeArray;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +080039
Owen Linf9a0a432011-08-17 22:07:43 +080040public class PhotoView extends GLView {
41 @SuppressWarnings("unused")
42 private static final String TAG = "PhotoView";
Bobby Georgescu915c2c52012-08-23 13:05:53 -070043 private final int mPlaceholderColor;
Owen Linf9a0a432011-08-17 22:07:43 +080044
45 public static final int INVALID_SIZE = -1;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +080046 public static final long INVALID_DATA_VERSION =
47 MediaObject.INVALID_DATA_VERSION;
Owen Linf9a0a432011-08-17 22:07:43 +080048
Chih-Chung Changc3b2d472012-04-19 20:14:11 +080049 public static class Size {
50 public int width;
51 public int height;
52 }
53
54 public interface Model extends TileImageView.Model {
Chih-Chung Changbd141b52012-04-26 10:10:49 +080055 public int getCurrentIndex();
56 public void moveTo(int index);
Chih-Chung Changc3b2d472012-04-19 20:14:11 +080057
58 // Returns the size for the specified picture. If the size information is
59 // not avaiable, width = height = 0.
60 public void getImageSize(int offset, Size size);
61
Owen Lin616a70f2012-05-07 16:35:53 +080062 // Returns the media item for the specified picture.
63 public MediaItem getMediaItem(int offset);
64
Chih-Chung Changc3b2d472012-04-19 20:14:11 +080065 // Returns the rotation for the specified picture.
66 public int getImageRotation(int offset);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +080067
68 // This amends the getScreenNail() method of TileImageView.Model to get
69 // ScreenNail at previous (negative offset) or next (positive offset)
70 // positions. Returns null if the specified ScreenNail is unavailable.
71 public ScreenNail getScreenNail(int offset);
Chih-Chung Changc3b2d472012-04-19 20:14:11 +080072
73 // Set this to true if we need the model to provide full images.
Chih-Chung Changb8be1e02012-04-17 20:35:14 +080074 public void setNeedFullImage(boolean enabled);
Chih-Chung Changbd141b52012-04-26 10:10:49 +080075
76 // Returns true if the item is the Camera preview.
77 public boolean isCamera(int offset);
Chih-Chung Changd9355112012-05-06 01:24:22 +080078
Angus Kong43a80fd2012-05-17 12:47:26 -070079 // Returns true if the item is the Panorama.
80 public boolean isPanorama(int offset);
81
Wu-cheng Lidbb6acc2012-08-19 17:04:02 +080082 // Returns true if the item is a static image that represents camera
83 // preview.
84 public boolean isStaticCamera(int offset);
85
Chih-Chung Changd9355112012-05-06 01:24:22 +080086 // Returns true if the item is a Video.
87 public boolean isVideo(int offset);
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +080088
Chih-Chung Chang6b891c62012-06-07 20:09:13 +080089 // Returns true if the item can be deleted.
90 public boolean isDeletable(int offset);
91
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +080092 public static final int LOADING_INIT = 0;
93 public static final int LOADING_COMPLETE = 1;
94 public static final int LOADING_FAIL = 2;
95
96 public int getLoadingState(int offset);
Chih-Chung Chang6b891c62012-06-07 20:09:13 +080097
98 // When data change happens, we need to decide which MediaItem to focus
99 // on.
100 //
101 // 1. If focus hint path != null, we try to focus on it if we can find
102 // it. This is used for undo a deletion, so we can focus on the
103 // undeleted item.
104 //
105 // 2. Otherwise try to focus on the MediaItem that is currently focused,
106 // if we can find it.
107 //
108 // 3. Otherwise try to focus on the previous MediaItem or the next
109 // MediaItem, depending on the value of focus hint direction.
110 public static final int FOCUS_HINT_NEXT = 0;
111 public static final int FOCUS_HINT_PREVIOUS = 1;
112 public void setFocusHintDirection(int direction);
113 public void setFocusHintPath(Path path);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800114 }
115
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800116 public interface Listener {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800117 public void onSingleTapUp(int x, int y);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800118 public void onFullScreenChanged(boolean full);
Chih-Chung Chang61f94712012-05-02 20:05:16 +0800119 public void onActionBarAllowed(boolean allowed);
Chih-Chung Change6251df2012-05-22 11:35:46 -0700120 public void onActionBarWanted();
Yuli Huangbd7c0162012-05-15 22:36:59 +0800121 public void onCurrentImageUpdated();
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800122 public void onDeleteImage(Path path, int offset);
123 public void onUndoDeleteImage();
124 public void onCommitDeleteImage();
Bobby Georgescu7eea4d32012-09-06 17:14:02 -0700125 public void onFilmModeChanged(boolean enabled);
Bobby Georgescuf4e22eb2012-10-02 18:34:08 -0700126 public void onPictureCenter(boolean isCamera);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800127 }
128
Chih-Chung Change6251df2012-05-22 11:35:46 -0700129 // The rules about orientation locking:
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800130 //
Chih-Chung Change6251df2012-05-22 11:35:46 -0700131 // (1) We need to lock the orientation if we are in page mode camera
132 // preview, so there is no (unwanted) rotation animation when the user
133 // rotates the device.
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800134 //
Chih-Chung Change6251df2012-05-22 11:35:46 -0700135 // (2) We need to unlock the orientation if we want to show the action bar
136 // because the action bar follows the system orientation.
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800137 //
Chih-Chung Change6251df2012-05-22 11:35:46 -0700138 // The rules about action bar:
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800139 //
Chih-Chung Change6251df2012-05-22 11:35:46 -0700140 // (1) If we are in film mode, we don't show action bar.
141 //
142 // (2) If we go from camera to gallery with capture animation, we show
143 // action bar.
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800144 private static final int MSG_CANCEL_EXTRA_SCALING = 2;
145 private static final int MSG_SWITCH_FOCUS = 3;
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800146 private static final int MSG_CAPTURE_ANIMATION_DONE = 4;
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800147 private static final int MSG_DELETE_ANIMATION_DONE = 5;
148 private static final int MSG_DELETE_DONE = 6;
Chih-Chung Chang6118af92012-06-22 20:56:04 +0800149 private static final int MSG_UNDO_BAR_TIMEOUT = 7;
150 private static final int MSG_UNDO_BAR_FULL_CAMERA = 8;
Owen Linf9a0a432011-08-17 22:07:43 +0800151
Owen Linf9a0a432011-08-17 22:07:43 +0800152 private static final float SWIPE_THRESHOLD = 300f;
153
154 private static final float DEFAULT_TEXT_SIZE = 20;
Chih-Chung Changcb4fb7c2012-03-21 16:19:21 +0800155 private static float TRANSITION_SCALE_FACTOR = 0.74f;
Chih-Chung Changd9355112012-05-06 01:24:22 +0800156 private static final int ICON_RATIO = 6;
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800157
158 // whether we want to apply card deck effect in page mode.
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800159 private static final boolean CARD_EFFECT = true;
Chih-Chung Changcb4fb7c2012-03-21 16:19:21 +0800160
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800161 // whether we want to apply offset effect in film mode.
162 private static final boolean OFFSET_EFFECT = true;
163
164 // Used to calculate the scaling factor for the card deck effect.
Chih-Chung Changcb4fb7c2012-03-21 16:19:21 +0800165 private ZInterpolator mScaleInterpolator = new ZInterpolator(0.5f);
166
167 // Used to calculate the alpha factor for the fading animation.
168 private AccelerateInterpolator mAlphaInterpolator =
169 new AccelerateInterpolator(0.9f);
Owen Linf9a0a432011-08-17 22:07:43 +0800170
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800171 // We keep this many previous ScreenNails. (also this many next ScreenNails)
172 public static final int SCREEN_NAIL_MAX = 3;
Owen Linf9a0a432011-08-17 22:07:43 +0800173
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800174 // These are constants for the delete gesture.
Bobby Georgescu3711d3e2012-10-06 17:39:23 -0700175 private static final int SWIPE_ESCAPE_VELOCITY = 2500; // dp/sec
176 private static final int MAX_DISMISS_VELOCITY = 4000; // dp/sec
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800177
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800178 // The picture entries, the valid index is from -SCREEN_NAIL_MAX to
179 // SCREEN_NAIL_MAX.
180 private final RangeArray<Picture> mPictures =
181 new RangeArray<Picture>(-SCREEN_NAIL_MAX, SCREEN_NAIL_MAX);
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800182 private Size[] mSizes = new Size[2 * SCREEN_NAIL_MAX + 1];
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800183
Chih-Chung Chang65757942012-05-06 03:25:16 +0800184 private final MyGestureListener mGestureListener;
Chih-Chung Chang3a028092012-03-14 17:39:42 +0800185 private final GestureRecognizer mGestureRecognizer;
Owen Linf9a0a432011-08-17 22:07:43 +0800186 private final PositionController mPositionController;
187
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800188 private Listener mListener;
Owen Linf9a0a432011-08-17 22:07:43 +0800189 private Model mModel;
Owen Linf9a0a432011-08-17 22:07:43 +0800190 private StringTexture mNoThumbnailText;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800191 private TileImageView mTileView;
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800192 private EdgeView mEdgeView;
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800193 private UndoBarView mUndoBar;
Owen Linf9a0a432011-08-17 22:07:43 +0800194 private Texture mVideoPlayIcon;
195
Owen Linf9a0a432011-08-17 22:07:43 +0800196 private SynchronizedHandler mHandler;
197
Chih-Chung Chang534b12f2012-03-21 19:01:30 +0800198 private boolean mCancelExtraScalingPending;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800199 private boolean mFilmMode = false;
Bobby Georgescuf4e22eb2012-10-02 18:34:08 -0700200 private boolean mWantPictureCenterCallbacks = false;
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800201 private int mDisplayRotation = 0;
202 private int mCompensation = 0;
Chih-Chung Chang9f44f352012-05-29 15:28:36 -0700203 private boolean mFullScreenCamera;
Chih-Chung Chang2ef46ed2012-05-08 17:57:33 +0800204 private Rect mCameraRelativeFrame = new Rect();
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800205 private Rect mCameraRect = new Rect();
Owen Linf9a0a432011-08-17 22:07:43 +0800206
Chih-Chung Changc3b2d472012-04-19 20:14:11 +0800207 // [mPrevBound, mNextBound] is the range of index for all pictures in the
208 // model, if we assume the index of current focused picture is 0. So if
209 // there are some previous pictures, mPrevBound < 0, and if there are some
210 // next pictures, mNextBound > 0.
211 private int mPrevBound;
212 private int mNextBound;
213
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800214 // This variable prevents us doing snapback until its values goes to 0. This
215 // happens if the user gesture is still in progress or we are in a capture
216 // animation.
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800217 private int mHolding;
218 private static final int HOLD_TOUCH_DOWN = 1;
Chih-Chung Chang18958c52012-05-01 02:58:59 +0800219 private static final int HOLD_CAPTURE_ANIMATION = 2;
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800220 private static final int HOLD_DELETE = 4;
221
222 // mTouchBoxIndex is the index of the box that is touched by the down
223 // gesture in film mode. The value Integer.MAX_VALUE means no box was
224 // touched.
225 private int mTouchBoxIndex = Integer.MAX_VALUE;
226 // Whether the box indicated by mTouchBoxIndex is deletable. Only meaningful
227 // if mTouchBoxIndex is not Integer.MAX_VALUE.
228 private boolean mTouchBoxDeletable;
Chih-Chung Chang517e1bd2012-06-19 17:34:50 +0800229 // This is the index of the last deleted item. This is only used as a hint
230 // to hide the undo button when we are too far away from the deleted
231 // item. The value Integer.MAX_VALUE means there is no such hint.
232 private int mUndoIndexHint = Integer.MAX_VALUE;
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800233
Owen Linb21b8e52012-08-24 12:25:57 +0800234 public PhotoView(AbstractGalleryActivity activity) {
Owen Linf9a0a432011-08-17 22:07:43 +0800235 mTileView = new TileImageView(activity);
236 addComponent(mTileView);
237 Context context = activity.getAndroidContext();
Bobby Georgescu915c2c52012-08-23 13:05:53 -0700238 mPlaceholderColor = context.getResources().getColor(
239 R.color.photo_placeholder);
Chih-Chung Chang532d93c2011-10-12 17:10:33 +0800240 mEdgeView = new EdgeView(context);
241 addComponent(mEdgeView);
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800242 mUndoBar = new UndoBarView(context);
243 addComponent(mUndoBar);
244 mUndoBar.setVisibility(GLView.INVISIBLE);
245 mUndoBar.setOnClickListener(new OnClickListener() {
246 @Override
247 public void onClick(GLView v) {
248 mListener.onUndoDeleteImage();
Chih-Chung Chang6118af92012-06-22 20:56:04 +0800249 hideUndoBar();
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800250 }
251 });
Owen Linf9a0a432011-08-17 22:07:43 +0800252 mNoThumbnailText = StringTexture.newInstance(
253 context.getString(R.string.no_thumbnail),
254 DEFAULT_TEXT_SIZE, Color.WHITE);
255
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800256 mHandler = new MyHandler(activity.getGLRoot());
Owen Linf9a0a432011-08-17 22:07:43 +0800257
Chih-Chung Chang65757942012-05-06 03:25:16 +0800258 mGestureListener = new MyGestureListener();
259 mGestureRecognizer = new GestureRecognizer(context, mGestureListener);
Owen Linf9a0a432011-08-17 22:07:43 +0800260
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800261 mPositionController = new PositionController(context,
262 new PositionController.Listener() {
Owen Lin28cb4162012-08-29 11:53:10 +0800263
264 @Override
265 public void invalidate() {
266 PhotoView.this.invalidate();
267 }
268
269 @Override
270 public boolean isHoldingDown() {
271 return (mHolding & HOLD_TOUCH_DOWN) != 0;
272 }
273
274 @Override
275 public boolean isHoldingDelete() {
276 return (mHolding & HOLD_DELETE) != 0;
277 }
278
279 @Override
280 public void onPull(int offset, int direction) {
281 mEdgeView.onPull(offset, direction);
282 }
283
284 @Override
285 public void onRelease() {
286 mEdgeView.onRelease();
287 }
288
289 @Override
290 public void onAbsorb(int velocity, int direction) {
291 mEdgeView.onAbsorb(velocity, direction);
292 }
293 });
Owen Linf9a0a432011-08-17 22:07:43 +0800294 mVideoPlayIcon = new ResourceTexture(context, R.drawable.ic_control_play);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800295 for (int i = -SCREEN_NAIL_MAX; i <= SCREEN_NAIL_MAX; i++) {
296 if (i == 0) {
297 mPictures.put(i, new FullPicture());
298 } else {
299 mPictures.put(i, new ScreenNailPicture(i));
300 }
301 }
Owen Linf9a0a432011-08-17 22:07:43 +0800302 }
303
Bobby Georgescub27df462012-09-27 23:55:44 -0700304 public void stopScrolling() {
305 mPositionController.stopScrolling();
306 }
307
Owen Linf9a0a432011-08-17 22:07:43 +0800308 public void setModel(Model model) {
Owen Linf9a0a432011-08-17 22:07:43 +0800309 mModel = model;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800310 mTileView.setModel(mModel);
Owen Linf9a0a432011-08-17 22:07:43 +0800311 }
312
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800313 class MyHandler extends SynchronizedHandler {
314 public MyHandler(GLRoot root) {
315 super(root);
Chih-Chung Changcb4fb7c2012-03-21 16:19:21 +0800316 }
317
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800318 @Override
319 public void handleMessage(Message message) {
320 switch (message.what) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800321 case MSG_CANCEL_EXTRA_SCALING: {
322 mGestureRecognizer.cancelScale();
323 mPositionController.setExtraScalingRange(false);
324 mCancelExtraScalingPending = false;
325 break;
326 }
327 case MSG_SWITCH_FOCUS: {
328 switchFocus();
329 break;
330 }
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800331 case MSG_CAPTURE_ANIMATION_DONE: {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800332 // message.arg1 is the offset parameter passed to
333 // switchWithCaptureAnimation().
334 captureAnimationDone(message.arg1);
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800335 break;
336 }
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800337 case MSG_DELETE_ANIMATION_DONE: {
338 // message.obj is the Path of the MediaItem which should be
339 // deleted. message.arg1 is the offset of the image.
340 mListener.onDeleteImage((Path) message.obj, message.arg1);
341 // Normally a box which finishes delete animation will hold
342 // position until the underlying MediaItem is actually
343 // deleted, and HOLD_DELETE will be cancelled that time. In
344 // case the MediaItem didn't actually get deleted in 2
345 // seconds, we will cancel HOLD_DELETE and make it bounce
346 // back.
347
348 // We make sure there is at most one MSG_DELETE_DONE
349 // in the handler.
350 mHandler.removeMessages(MSG_DELETE_DONE);
351 Message m = mHandler.obtainMessage(MSG_DELETE_DONE);
352 mHandler.sendMessageDelayed(m, 2000);
Chih-Chung Chang6118af92012-06-22 20:56:04 +0800353
354 int numberOfPictures = mNextBound - mPrevBound + 1;
355 if (numberOfPictures == 2) {
356 if (mModel.isCamera(mNextBound)
357 || mModel.isCamera(mPrevBound)) {
358 numberOfPictures--;
359 }
360 }
361 showUndoBar(numberOfPictures <= 1);
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800362 break;
363 }
364 case MSG_DELETE_DONE: {
365 if (!mHandler.hasMessages(MSG_DELETE_ANIMATION_DONE)) {
366 mHolding &= ~HOLD_DELETE;
367 snapback();
368 }
369 break;
370 }
Chih-Chung Chang6118af92012-06-22 20:56:04 +0800371 case MSG_UNDO_BAR_TIMEOUT: {
Chih-Chung Chang517e1bd2012-06-19 17:34:50 +0800372 checkHideUndoBar(UNDO_BAR_TIMEOUT);
373 break;
374 }
Chih-Chung Chang6118af92012-06-22 20:56:04 +0800375 case MSG_UNDO_BAR_FULL_CAMERA: {
376 checkHideUndoBar(UNDO_BAR_FULL_CAMERA);
377 break;
378 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800379 default: throw new AssertionError(message.what);
Owen Linf9a0a432011-08-17 22:07:43 +0800380 }
381 }
Owen Lin28cb4162012-08-29 11:53:10 +0800382 }
Owen Linf9a0a432011-08-17 22:07:43 +0800383
Bobby Georgescuf4e22eb2012-10-02 18:34:08 -0700384 public void setWantPictureCenterCallbacks(boolean wanted) {
385 mWantPictureCenterCallbacks = wanted;
Bobby Georgescub27df462012-09-27 23:55:44 -0700386 }
387
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800388 ////////////////////////////////////////////////////////////////////////////
389 // Data/Image change notifications
390 ////////////////////////////////////////////////////////////////////////////
Owen Linf9a0a432011-08-17 22:07:43 +0800391
Chih-Chung Chang214993d2012-05-11 17:20:53 +0800392 public void notifyDataChange(int[] fromIndex, int prevBound, int nextBound) {
Chih-Chung Changc3b2d472012-04-19 20:14:11 +0800393 mPrevBound = prevBound;
394 mNextBound = nextBound;
395
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800396 // Update mTouchBoxIndex
397 if (mTouchBoxIndex != Integer.MAX_VALUE) {
398 int k = mTouchBoxIndex;
399 mTouchBoxIndex = Integer.MAX_VALUE;
400 for (int i = 0; i < 2 * SCREEN_NAIL_MAX + 1; i++) {
401 if (fromIndex[i] == k) {
402 mTouchBoxIndex = i - SCREEN_NAIL_MAX;
403 break;
404 }
405 }
406 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800407
Chih-Chung Chang517e1bd2012-06-19 17:34:50 +0800408 // Hide undo button if we are too far away
409 if (mUndoIndexHint != Integer.MAX_VALUE) {
410 if (Math.abs(mUndoIndexHint - mModel.getCurrentIndex()) >= 3) {
411 hideUndoBar();
412 }
413 }
414
Chih-Chung Changb8be1e02012-04-17 20:35:14 +0800415 // Update the ScreenNails.
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800416 for (int i = -SCREEN_NAIL_MAX; i <= SCREEN_NAIL_MAX; i++) {
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800417 Picture p = mPictures.get(i);
418 p.reload();
419 mSizes[i + SCREEN_NAIL_MAX] = p.getSize();
420 }
421
422 boolean wasDeleting = mPositionController.hasDeletingBox();
423
424 // Move the boxes
425 mPositionController.moveBox(fromIndex, mPrevBound < 0, mNextBound > 0,
426 mModel.isCamera(0), mSizes);
427
428 for (int i = -SCREEN_NAIL_MAX; i <= SCREEN_NAIL_MAX; i++) {
429 setPictureSize(i);
430 }
431
432 boolean isDeleting = mPositionController.hasDeletingBox();
433
434 // If the deletion is done, make HOLD_DELETE persist for only the time
435 // needed for a snapback animation.
436 if (wasDeleting && !isDeleting) {
437 mHandler.removeMessages(MSG_DELETE_DONE);
438 Message m = mHandler.obtainMessage(MSG_DELETE_DONE);
439 mHandler.sendMessageDelayed(
440 m, PositionController.SNAPBACK_ANIMATION_TIME);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800441 }
442
443 invalidate();
444 }
445
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800446 public boolean isDeleting() {
447 return (mHolding & HOLD_DELETE) != 0
448 && mPositionController.hasDeletingBox();
449 }
450
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800451 public void notifyImageChange(int index) {
Yuli Huangbd7c0162012-05-15 22:36:59 +0800452 if (index == 0) {
453 mListener.onCurrentImageUpdated();
454 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800455 mPictures.get(index).reload();
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800456 setPictureSize(index);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800457 invalidate();
458 }
459
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800460 private void setPictureSize(int index) {
461 Picture p = mPictures.get(index);
462 mPositionController.setImageSize(index, p.getSize(),
463 index == 0 && p.isCamera() ? mCameraRect : null);
464 }
465
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800466 @Override
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800467 protected void onLayout(
468 boolean changeSize, int left, int top, int right, int bottom) {
Chih-Chung Chang2ef46ed2012-05-08 17:57:33 +0800469 int w = right - left;
470 int h = bottom - top;
471 mTileView.layout(0, 0, w, h);
472 mEdgeView.layout(0, 0, w, h);
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800473 mUndoBar.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
474 mUndoBar.layout(0, h - mUndoBar.getMeasuredHeight(), w, h);
Chih-Chung Chang2ef46ed2012-05-08 17:57:33 +0800475
476 GLRoot root = getGLRoot();
477 int displayRotation = root.getDisplayRotation();
478 int compensation = root.getCompensation();
479 if (mDisplayRotation != displayRotation
480 || mCompensation != compensation) {
481 mDisplayRotation = displayRotation;
482 mCompensation = compensation;
483
Chih-Chung Chang3b4a8ae2012-05-09 21:17:10 +0800484 // We need to change the size and rotation of the Camera ScreenNail,
485 // but we don't want it to animate because the size doen't actually
Chih-Chung Chang2ef46ed2012-05-08 17:57:33 +0800486 // change in the eye of the user.
487 for (int i = -SCREEN_NAIL_MAX; i <= SCREEN_NAIL_MAX; i++) {
488 Picture p = mPictures.get(i);
489 if (p.isCamera()) {
Chih-Chung Chang9f44f352012-05-29 15:28:36 -0700490 p.forceSize();
Chih-Chung Chang2ef46ed2012-05-08 17:57:33 +0800491 }
492 }
493 }
494
Chih-Chung Chang9f44f352012-05-29 15:28:36 -0700495 updateCameraRect();
496 mPositionController.setConstrainedFrame(mCameraRect);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800497 if (changeSize) {
498 mPositionController.setViewSize(getWidth(), getHeight());
499 }
500 }
501
Chih-Chung Chang9f44f352012-05-29 15:28:36 -0700502 // Update the camera rectangle due to layout change or camera relative frame
503 // change.
504 private void updateCameraRect() {
Chih-Chung Chang2ef46ed2012-05-08 17:57:33 +0800505 // Get the width and height in framework orientation because the given
506 // mCameraRelativeFrame is in that coordinates.
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800507 int w = getWidth();
508 int h = getHeight();
Chih-Chung Chang2ef46ed2012-05-08 17:57:33 +0800509 if (mCompensation % 180 != 0) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800510 int tmp = w;
511 w = h;
512 h = tmp;
513 }
Chih-Chung Chang2ef46ed2012-05-08 17:57:33 +0800514 int l = mCameraRelativeFrame.left;
515 int t = mCameraRelativeFrame.top;
516 int r = mCameraRelativeFrame.right;
517 int b = mCameraRelativeFrame.bottom;
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800518
Chih-Chung Chang2ef46ed2012-05-08 17:57:33 +0800519 // Now convert it to the coordinates we are using.
520 switch (mCompensation) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800521 case 0: mCameraRect.set(l, t, r, b); break;
522 case 90: mCameraRect.set(h - b, l, h - t, r); break;
523 case 180: mCameraRect.set(w - r, h - b, w - l, h - t); break;
524 case 270: mCameraRect.set(t, w - r, b, w - l); break;
525 }
526
Chih-Chung Chang2ef46ed2012-05-08 17:57:33 +0800527 Log.d(TAG, "compensation = " + mCompensation
528 + ", CameraRelativeFrame = " + mCameraRelativeFrame
529 + ", mCameraRect = " + mCameraRect);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800530 }
531
Chih-Chung Chang2ef46ed2012-05-08 17:57:33 +0800532 public void setCameraRelativeFrame(Rect frame) {
533 mCameraRelativeFrame.set(frame);
Chih-Chung Chang9f44f352012-05-29 15:28:36 -0700534 updateCameraRect();
535 // Originally we do
536 // mPositionController.setConstrainedFrame(mCameraRect);
537 // here, but it is moved to a parameter of the setImageSize() call, so
538 // it can be updated atomically with the CameraScreenNail's size change.
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800539 }
540
541 // Returns the rotation we need to do to the camera texture before drawing
542 // it to the canvas, assuming the camera texture is correct when the device
543 // is in its natural orientation.
544 private int getCameraRotation() {
545 return (mCompensation - mDisplayRotation + 360) % 360;
546 }
547
Angus Kong43a80fd2012-05-17 12:47:26 -0700548 private int getPanoramaRotation() {
John Reck866ed7a2012-10-09 22:10:13 -0700549 // Panorama only support rotations of 0 and 90, so if it is greater
550 // than that flip the output surface texture to compensate
551 if (mDisplayRotation > 180)
552 return (mCompensation + 180) % 360;
Angus Kong43a80fd2012-05-17 12:47:26 -0700553 return mCompensation;
554 }
555
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800556 ////////////////////////////////////////////////////////////////////////////
557 // Pictures
558 ////////////////////////////////////////////////////////////////////////////
559
560 private interface Picture {
561 void reload();
562 void draw(GLCanvas canvas, Rect r);
Chih-Chung Changb8be1e02012-04-17 20:35:14 +0800563 void setScreenNail(ScreenNail s);
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800564 boolean isCamera(); // whether the picture is a camera preview
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800565 boolean isDeletable(); // whether the picture can be deleted
Chih-Chung Chang9f44f352012-05-29 15:28:36 -0700566 void forceSize(); // called when mCompensation changes
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800567 Size getSize();
Ahbong Chang78179792012-07-30 11:34:13 +0800568 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800569
570 class FullPicture implements Picture {
571 private int mRotation;
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800572 private boolean mIsCamera;
Angus Kong43a80fd2012-05-17 12:47:26 -0700573 private boolean mIsPanorama;
Wu-cheng Lidbb6acc2012-08-19 17:04:02 +0800574 private boolean mIsStaticCamera;
Chih-Chung Changd9355112012-05-06 01:24:22 +0800575 private boolean mIsVideo;
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800576 private boolean mIsDeletable;
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800577 private int mLoadingState = Model.LOADING_INIT;
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800578 private Size mSize = new Size();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800579
580 @Override
581 public void reload() {
582 // mImageWidth and mImageHeight will get updated
Owen Linf9a0a432011-08-17 22:07:43 +0800583 mTileView.notifyModelInvalidated();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800584
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800585 mIsCamera = mModel.isCamera(0);
Angus Kong43a80fd2012-05-17 12:47:26 -0700586 mIsPanorama = mModel.isPanorama(0);
Wu-cheng Lidbb6acc2012-08-19 17:04:02 +0800587 mIsStaticCamera = mModel.isStaticCamera(0);
Chih-Chung Changd9355112012-05-06 01:24:22 +0800588 mIsVideo = mModel.isVideo(0);
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800589 mIsDeletable = mModel.isDeletable(0);
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800590 mLoadingState = mModel.getLoadingState(0);
Chih-Chung Changc3b2d472012-04-19 20:14:11 +0800591 setScreenNail(mModel.getScreenNail(0));
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800592 updateSize();
Chih-Chung Chang9f44f352012-05-29 15:28:36 -0700593 }
594
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800595 @Override
596 public Size getSize() {
597 return mSize;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800598 }
599
600 @Override
Chih-Chung Chang9f44f352012-05-29 15:28:36 -0700601 public void forceSize() {
602 updateSize();
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800603 mPositionController.forceImageSize(0, mSize);
Chih-Chung Chang9f44f352012-05-29 15:28:36 -0700604 }
605
606 private void updateSize() {
Angus Kong43a80fd2012-05-17 12:47:26 -0700607 if (mIsPanorama) {
608 mRotation = getPanoramaRotation();
Wu-cheng Lidbb6acc2012-08-19 17:04:02 +0800609 } else if (mIsCamera && !mIsStaticCamera) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800610 mRotation = getCameraRotation();
611 } else {
612 mRotation = mModel.getImageRotation(0);
613 }
614
615 int w = mTileView.mImageWidth;
616 int h = mTileView.mImageHeight;
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800617 mSize.width = getRotated(mRotation, w, h);
618 mSize.height = getRotated(mRotation, h, w);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800619 }
620
621 @Override
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800622 public void draw(GLCanvas canvas, Rect r) {
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800623 drawTileView(canvas, r);
624
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800625 // We want to have the following transitions:
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800626 // (1) Move camera preview out of its place: switch to film mode
627 // (2) Move camera preview into its place: switch to page mode
628 // The extra mWasCenter check makes sure (1) does not apply if in
629 // page mode, we move _to_ the camera preview from another picture.
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800630
631 // Holdings except touch-down prevent the transitions.
Chih-Chung Chang18958c52012-05-01 02:58:59 +0800632 if ((mHolding & ~HOLD_TOUCH_DOWN) != 0) return;
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800633
Bobby Georgescuf4e22eb2012-10-02 18:34:08 -0700634 if (mWantPictureCenterCallbacks && mPositionController.isCenter()) {
Bobby Georgescu639095c2012-10-08 13:37:41 -0700635 mListener.onPictureCenter(mIsCamera);
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800636 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800637 }
638
639 @Override
Chih-Chung Changb8be1e02012-04-17 20:35:14 +0800640 public void setScreenNail(ScreenNail s) {
Chih-Chung Changb8be1e02012-04-17 20:35:14 +0800641 mTileView.setScreenNail(s);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800642 }
643
644 @Override
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800645 public boolean isCamera() {
646 return mIsCamera;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800647 }
648
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800649 @Override
650 public boolean isDeletable() {
651 return mIsDeletable;
652 }
653
Chih-Chung Changd9355112012-05-06 01:24:22 +0800654 private void drawTileView(GLCanvas canvas, Rect r) {
Chih-Chung Changba12eae2012-05-07 02:29:37 +0800655 float imageScale = mPositionController.getImageScale();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800656 int viewW = getWidth();
657 int viewH = getHeight();
Chih-Chung Changd9355112012-05-06 01:24:22 +0800658 float cx = r.exactCenterX();
659 float cy = r.exactCenterY();
Chih-Chung Changba12eae2012-05-07 02:29:37 +0800660 float scale = 1f; // the scaling factor due to card effect
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800661
Chih-Chung Changd9355112012-05-06 01:24:22 +0800662 canvas.save(GLCanvas.SAVE_FLAG_MATRIX | GLCanvas.SAVE_FLAG_ALPHA);
Chih-Chung Changba12eae2012-05-07 02:29:37 +0800663 float filmRatio = mPositionController.getFilmRatio();
664 boolean wantsCardEffect = CARD_EFFECT && !mIsCamera
Yuli Huangf320b842012-05-16 01:38:06 +0800665 && filmRatio != 1f && !mPictures.get(-1).isCamera()
666 && !mPositionController.inOpeningAnimation();
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800667 boolean wantsOffsetEffect = OFFSET_EFFECT && mIsDeletable
668 && filmRatio == 1f && r.centerY() != viewH / 2;
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800669 if (wantsCardEffect) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800670 // Calculate the move-out progress value.
671 int left = r.left;
672 int right = r.right;
673 float progress = calculateMoveOutProgress(left, right, viewW);
674 progress = Utils.clamp(progress, -1f, 1f);
675
676 // We only want to apply the fading animation if the scrolling
677 // movement is to the right.
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800678 if (progress < 0) {
Chih-Chung Changba12eae2012-05-07 02:29:37 +0800679 scale = getScrollScale(progress);
680 float alpha = getScrollAlpha(progress);
681 scale = interpolate(filmRatio, scale, 1f);
682 alpha = interpolate(filmRatio, alpha, 1f);
Chih-Chung Changd9355112012-05-06 01:24:22 +0800683
Chih-Chung Changba12eae2012-05-07 02:29:37 +0800684 imageScale *= scale;
685 canvas.multiplyAlpha(alpha);
686
687 float cxPage; // the cx value in page mode
Chih-Chung Changd9355112012-05-06 01:24:22 +0800688 if (right - left <= viewW) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800689 // If the picture is narrower than the view, keep it at
690 // the center of the view.
Chih-Chung Changba12eae2012-05-07 02:29:37 +0800691 cxPage = viewW / 2f;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800692 } else {
693 // If the picture is wider than the view (it's
694 // zoomed-in), keep the left edge of the object align
695 // the the left edge of the view.
Chih-Chung Changba12eae2012-05-07 02:29:37 +0800696 cxPage = (right - left) * scale / 2f;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800697 }
Chih-Chung Changba12eae2012-05-07 02:29:37 +0800698 cx = interpolate(filmRatio, cxPage, cx);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800699 }
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800700 } else if (wantsOffsetEffect) {
701 float offset = (float) (r.centerY() - viewH / 2) / viewH;
702 float alpha = getOffsetAlpha(offset);
703 canvas.multiplyAlpha(alpha);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800704 }
705
Chih-Chung Changd9355112012-05-06 01:24:22 +0800706 // Draw the tile view.
Chih-Chung Changba12eae2012-05-07 02:29:37 +0800707 setTileViewPosition(cx, cy, viewW, viewH, imageScale);
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800708 renderChild(canvas, mTileView);
Chih-Chung Changd9355112012-05-06 01:24:22 +0800709
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800710 // Draw the play video icon and the message.
711 canvas.translate((int) (cx + 0.5f), (int) (cy + 0.5f));
712 int s = (int) (scale * Math.min(r.width(), r.height()) + 0.5f);
Mangesh Ghiwarec1c67ea2012-09-30 12:58:56 -0700713 if (mIsVideo) drawVideoPlayIcon(canvas, s);
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800714 if (mLoadingState == Model.LOADING_FAIL) {
715 drawLoadingFailMessage(canvas);
Chih-Chung Changd9355112012-05-06 01:24:22 +0800716 }
717
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800718 // Draw a debug indicator showing which picture has focus (index ==
719 // 0).
720 //canvas.fillRect(-10, -10, 20, 20, 0x80FF00FF);
721
Chih-Chung Changd9355112012-05-06 01:24:22 +0800722 canvas.restore();
723 }
724
725 // Set the position of the tile view
726 private void setTileViewPosition(float cx, float cy,
727 int viewW, int viewH, float scale) {
728 // Find out the bitmap coordinates of the center of the view
729 int imageW = mPositionController.getImageWidth();
730 int imageH = mPositionController.getImageHeight();
731 int centerX = (int) (imageW / 2f + (viewW / 2f - cx) / scale + 0.5f);
732 int centerY = (int) (imageH / 2f + (viewH / 2f - cy) / scale + 0.5f);
733
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800734 int inverseX = imageW - centerX;
735 int inverseY = imageH - centerY;
Chih-Chung Changd9355112012-05-06 01:24:22 +0800736 int x, y;
737 switch (mRotation) {
738 case 0: x = centerX; y = centerY; break;
739 case 90: x = centerY; y = inverseX; break;
740 case 180: x = inverseX; y = inverseY; break;
741 case 270: x = inverseY; y = centerX; break;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800742 default:
Chih-Chung Changd9355112012-05-06 01:24:22 +0800743 throw new RuntimeException(String.valueOf(mRotation));
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800744 }
Chih-Chung Changd9355112012-05-06 01:24:22 +0800745 mTileView.setPosition(x, y, scale, mRotation);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800746 }
Owen Linf9a0a432011-08-17 22:07:43 +0800747 }
748
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800749 private class ScreenNailPicture implements Picture {
750 private int mIndex;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800751 private int mRotation;
752 private ScreenNail mScreenNail;
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800753 private boolean mIsCamera;
Angus Kong43a80fd2012-05-17 12:47:26 -0700754 private boolean mIsPanorama;
Wu-cheng Lidbb6acc2012-08-19 17:04:02 +0800755 private boolean mIsStaticCamera;
Chih-Chung Changd9355112012-05-06 01:24:22 +0800756 private boolean mIsVideo;
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800757 private boolean mIsDeletable;
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800758 private int mLoadingState = Model.LOADING_INIT;
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800759 private Size mSize = new Size();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800760
761 public ScreenNailPicture(int index) {
762 mIndex = index;
763 }
764
765 @Override
766 public void reload() {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800767 mIsCamera = mModel.isCamera(mIndex);
Angus Kong43a80fd2012-05-17 12:47:26 -0700768 mIsPanorama = mModel.isPanorama(mIndex);
Wu-cheng Lidbb6acc2012-08-19 17:04:02 +0800769 mIsStaticCamera = mModel.isStaticCamera(mIndex);
Chih-Chung Changd9355112012-05-06 01:24:22 +0800770 mIsVideo = mModel.isVideo(mIndex);
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800771 mIsDeletable = mModel.isDeletable(mIndex);
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800772 mLoadingState = mModel.getLoadingState(mIndex);
Chih-Chung Changc3b2d472012-04-19 20:14:11 +0800773 setScreenNail(mModel.getScreenNail(mIndex));
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800774 updateSize();
775 }
776
777 @Override
778 public Size getSize() {
779 return mSize;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800780 }
781
782 @Override
783 public void draw(GLCanvas canvas, Rect r) {
784 if (mScreenNail == null) {
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800785 // Draw a placeholder rectange if there should be a picture in
786 // this position (but somehow there isn't).
Chih-Chung Changc3b2d472012-04-19 20:14:11 +0800787 if (mIndex >= mPrevBound && mIndex <= mNextBound) {
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800788 drawPlaceHolder(canvas, r);
Chih-Chung Changc3b2d472012-04-19 20:14:11 +0800789 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800790 return;
791 }
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800792 int w = getWidth();
793 int h = getHeight();
794 if (r.left >= w || r.right <= 0 || r.top >= h || r.bottom <= 0) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800795 mScreenNail.noDraw();
796 return;
797 }
798
Chih-Chung Changba12eae2012-05-07 02:29:37 +0800799 float filmRatio = mPositionController.getFilmRatio();
800 boolean wantsCardEffect = CARD_EFFECT && mIndex > 0
801 && filmRatio != 1f && !mPictures.get(0).isCamera();
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800802 boolean wantsOffsetEffect = OFFSET_EFFECT && mIsDeletable
803 && filmRatio == 1f && r.centerY() != h / 2;
Chih-Chung Changba12eae2012-05-07 02:29:37 +0800804 int cx = wantsCardEffect
805 ? (int) (interpolate(filmRatio, w / 2, r.centerX()) + 0.5f)
806 : r.centerX();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800807 int cy = r.centerY();
Chih-Chung Changba12eae2012-05-07 02:29:37 +0800808 canvas.save(GLCanvas.SAVE_FLAG_MATRIX | GLCanvas.SAVE_FLAG_ALPHA);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800809 canvas.translate(cx, cy);
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800810 if (wantsCardEffect) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800811 float progress = (float) (w / 2 - r.centerX()) / w;
812 progress = Utils.clamp(progress, -1, 1);
813 float alpha = getScrollAlpha(progress);
814 float scale = getScrollScale(progress);
Chih-Chung Changba12eae2012-05-07 02:29:37 +0800815 alpha = interpolate(filmRatio, alpha, 1f);
816 scale = interpolate(filmRatio, scale, 1f);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800817 canvas.multiplyAlpha(alpha);
818 canvas.scale(scale, scale, 1);
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800819 } else if (wantsOffsetEffect) {
820 float offset = (float) (r.centerY() - h / 2) / h;
821 float alpha = getOffsetAlpha(offset);
822 canvas.multiplyAlpha(alpha);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800823 }
824 if (mRotation != 0) {
825 canvas.rotate(mRotation, 0, 0, 1);
826 }
Chih-Chung Changba12eae2012-05-07 02:29:37 +0800827 int drawW = getRotated(mRotation, r.width(), r.height());
828 int drawH = getRotated(mRotation, r.height(), r.width());
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800829 mScreenNail.draw(canvas, -drawW / 2, -drawH / 2, drawW, drawH);
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800830 if (isScreenNailAnimating()) {
831 invalidate();
832 }
833 int s = Math.min(drawW, drawH);
Mangesh Ghiwarec1c67ea2012-09-30 12:58:56 -0700834 if (mIsVideo) drawVideoPlayIcon(canvas, s);
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800835 if (mLoadingState == Model.LOADING_FAIL) {
836 drawLoadingFailMessage(canvas);
837 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800838 canvas.restore();
839 }
840
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800841 private boolean isScreenNailAnimating() {
842 return (mScreenNail instanceof BitmapScreenNail)
843 && ((BitmapScreenNail) mScreenNail).isAnimating();
844 }
845
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800846 @Override
Chih-Chung Changb8be1e02012-04-17 20:35:14 +0800847 public void setScreenNail(ScreenNail s) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800848 mScreenNail = s;
Chih-Chung Chang9f44f352012-05-29 15:28:36 -0700849 }
850
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800851 @Override
Chih-Chung Chang9f44f352012-05-29 15:28:36 -0700852 public void forceSize() {
853 updateSize();
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800854 mPositionController.forceImageSize(mIndex, mSize);
Chih-Chung Chang9f44f352012-05-29 15:28:36 -0700855 }
856
857 private void updateSize() {
Angus Kong43a80fd2012-05-17 12:47:26 -0700858 if (mIsPanorama) {
859 mRotation = getPanoramaRotation();
Wu-cheng Lidbb6acc2012-08-19 17:04:02 +0800860 } else if (mIsCamera && !mIsStaticCamera) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800861 mRotation = getCameraRotation();
862 } else {
863 mRotation = mModel.getImageRotation(mIndex);
864 }
Chih-Chung Changc3b2d472012-04-19 20:14:11 +0800865
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800866 if (mScreenNail != null) {
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800867 mSize.width = mScreenNail.getWidth();
868 mSize.height = mScreenNail.getHeight();
869 } else {
Chih-Chung Changc3b2d472012-04-19 20:14:11 +0800870 // If we don't have ScreenNail available, we can still try to
871 // get the size information of it.
872 mModel.getImageSize(mIndex, mSize);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800873 }
Chih-Chung Changc3b2d472012-04-19 20:14:11 +0800874
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800875 int w = mSize.width;
876 int h = mSize.height;
877 mSize.width = getRotated(mRotation, w, h);
878 mSize.height = getRotated(mRotation, h, w);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800879 }
880
881 @Override
Chih-Chung Chang2c617382012-04-20 20:06:19 +0800882 public boolean isCamera() {
883 return mIsCamera;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800884 }
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800885
886 @Override
887 public boolean isDeletable() {
888 return mIsDeletable;
889 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800890 }
891
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800892 // Draw a gray placeholder in the specified rectangle.
893 private void drawPlaceHolder(GLCanvas canvas, Rect r) {
Bobby Georgescu915c2c52012-08-23 13:05:53 -0700894 canvas.fillRect(r.left, r.top, r.width(), r.height(), mPlaceholderColor);
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800895 }
896
Chih-Chung Changd9355112012-05-06 01:24:22 +0800897 // Draw the video play icon (in the place where the spinner was)
898 private void drawVideoPlayIcon(GLCanvas canvas, int side) {
899 int s = side / ICON_RATIO;
900 // Draw the video play icon at the center
901 mVideoPlayIcon.draw(canvas, -s / 2, -s / 2, s, s);
902 }
903
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800904 // Draw the "no thumbnail" message
905 private void drawLoadingFailMessage(GLCanvas canvas) {
906 StringTexture m = mNoThumbnailText;
907 m.draw(canvas, -m.getWidth() / 2, -m.getHeight() / 2);
908 }
909
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800910 private static int getRotated(int degree, int original, int theother) {
911 return (degree % 180 == 0) ? original : theother;
912 }
913
914 ////////////////////////////////////////////////////////////////////////////
915 // Gestures Handling
916 ////////////////////////////////////////////////////////////////////////////
917
Owen Linf9a0a432011-08-17 22:07:43 +0800918 @Override
919 protected boolean onTouch(MotionEvent event) {
Chih-Chung Chang3a028092012-03-14 17:39:42 +0800920 mGestureRecognizer.onTouchEvent(event);
Owen Linf9a0a432011-08-17 22:07:43 +0800921 return true;
922 }
923
Chih-Chung Chang3a028092012-03-14 17:39:42 +0800924 private class MyGestureListener implements GestureRecognizer.Listener {
925 private boolean mIgnoreUpEvent = false;
Chih-Chung Chang099989b2012-04-18 20:08:57 +0800926 // If we can change mode for this scale gesture.
927 private boolean mCanChangeMode;
Chih-Chung Chang18958c52012-05-01 02:58:59 +0800928 // If we have changed the film mode in this scaling gesture.
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800929 private boolean mModeChanged;
Chih-Chung Chang33f85672012-05-01 15:35:02 +0800930 // If this scaling gesture should be ignored.
931 private boolean mIgnoreScalingGesture;
Chih-Chung Chang17ffedd2012-05-03 18:22:59 +0800932 // whether the down action happened while the view is scrolling.
933 private boolean mDownInScrolling;
Chih-Chung Chang65757942012-05-06 03:25:16 +0800934 // If we should ignore all gestures other than onSingleTapUp.
935 private boolean mIgnoreSwipingGesture;
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800936 // If a scrolling has happened after a down gesture.
937 private boolean mScrolledAfterDown;
938 // If the first scrolling move is in X direction. In the film mode, X
939 // direction scrolling is normal scrolling. but Y direction scrolling is
940 // a delete gesture.
941 private boolean mFirstScrollX;
942 // The accumulated Y delta that has been sent to mPositionController.
943 private int mDeltaY;
Chih-Chung Chang2ce59cb2012-06-18 17:24:58 +0800944 // The accumulated scaling change from a scaling gesture.
945 private float mAccScale;
Bobby Georgescu499ac9e2012-10-03 13:31:33 -0700946 // If an onFling happened after the last onDown
947 private boolean mHadFling;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800948
Owen Linf9a0a432011-08-17 22:07:43 +0800949 @Override
Chih-Chung Chang3a028092012-03-14 17:39:42 +0800950 public boolean onSingleTapUp(float x, float y) {
Hung-ying Tyan4d47b342012-08-29 14:15:32 +0800951 // On crespo running Android 2.3.6 (gingerbread), a pinch out gesture results in the
952 // following call sequence: onDown(), onUp() and then onSingleTapUp(). The correct
953 // sequence for a single-tap-up gesture should be: onDown(), onSingleTapUp() and onUp().
954 // The call sequence for a pinch out gesture in JB is: onDown(), then onUp() and there's
955 // no onSingleTapUp(). Base on these observations, the following condition is added to
956 // filter out the false alarm where onSingleTapUp() is called within a pinch out
957 // gesture. The framework fix went into ICS. Refer to b/4588114.
958 if (Build.VERSION.SDK_INT < ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH) {
959 if ((mHolding & HOLD_TOUCH_DOWN) == 0) {
960 return true;
961 }
962 }
963
Chih-Chung Changba12eae2012-05-07 02:29:37 +0800964 // We do this in addition to onUp() because we want the snapback of
965 // setFilmMode to happen.
966 mHolding &= ~HOLD_TOUCH_DOWN;
967
Chih-Chung Chang17ffedd2012-05-03 18:22:59 +0800968 if (mFilmMode && !mDownInScrolling) {
969 switchToHitPicture((int) (x + 0.5f), (int) (y + 0.5f));
Wu-cheng Li743f8152012-09-27 14:01:39 +0800970
971 // If this is a lock screen photo, let the listener handle the
972 // event. Tapping on lock screen photo should take the user
973 // directly to the lock screen.
974 MediaItem item = mModel.getMediaItem(0);
975 int supported = 0;
976 if (item != null) supported = item.getSupportedOperations();
Bobby Georgescuc7e3c762012-09-27 16:36:03 -0700977 if ((supported & MediaItem.SUPPORT_ACTION) == 0) {
Wu-cheng Li743f8152012-09-27 14:01:39 +0800978 setFilmMode(false);
979 mIgnoreUpEvent = true;
980 return true;
981 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800982 }
983
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800984 if (mListener != null) {
Chih-Chung Chang2ef46ed2012-05-08 17:57:33 +0800985 // Do the inverse transform of the touch coordinates.
986 Matrix m = getGLRoot().getCompensationMatrix();
987 Matrix inv = new Matrix();
988 m.invert(inv);
989 float[] pts = new float[] {x, y};
990 inv.mapPoints(pts);
991 mListener.onSingleTapUp((int) (pts[0] + 0.5f), (int) (pts[1] + 0.5f));
Chih-Chung Chang3a028092012-03-14 17:39:42 +0800992 }
993 return true;
994 }
995
996 @Override
997 public boolean onDoubleTap(float x, float y) {
Chih-Chung Chang65757942012-05-06 03:25:16 +0800998 if (mIgnoreSwipingGesture) return true;
Chih-Chung Chang61f94712012-05-02 20:05:16 +0800999 if (mPictures.get(0).isCamera()) return false;
Chih-Chung Chang3a028092012-03-14 17:39:42 +08001000 PositionController controller = mPositionController;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001001 float scale = controller.getImageScale();
Chih-Chung Chang3a028092012-03-14 17:39:42 +08001002 // onDoubleTap happened on the second ACTION_DOWN.
1003 // We need to ignore the next UP event.
1004 mIgnoreUpEvent = true;
1005 if (scale <= 1.0f || controller.isAtMinimalScale()) {
1006 controller.zoomIn(x, y, Math.max(1.5f, scale * 1.5f));
1007 } else {
1008 controller.resetToFullView();
1009 }
1010 return true;
1011 }
1012
1013 @Override
Chih-Chung Chang6b891c62012-06-07 20:09:13 +08001014 public boolean onScroll(float dx, float dy, float totalX, float totalY) {
Chih-Chung Chang65757942012-05-06 03:25:16 +08001015 if (mIgnoreSwipingGesture) return true;
Chih-Chung Chang6b891c62012-06-07 20:09:13 +08001016 if (!mScrolledAfterDown) {
1017 mScrolledAfterDown = true;
1018 mFirstScrollX = (Math.abs(dx) > Math.abs(dy));
1019 }
1020
1021 int dxi = (int) (-dx + 0.5f);
1022 int dyi = (int) (-dy + 0.5f);
1023 if (mFilmMode) {
1024 if (mFirstScrollX) {
1025 mPositionController.scrollFilmX(dxi);
1026 } else {
1027 if (mTouchBoxIndex == Integer.MAX_VALUE) return true;
1028 int newDeltaY = calculateDeltaY(totalY);
1029 int d = newDeltaY - mDeltaY;
1030 if (d != 0) {
1031 mPositionController.scrollFilmY(mTouchBoxIndex, d);
1032 mDeltaY = newDeltaY;
1033 }
1034 }
1035 } else {
1036 mPositionController.scrollPage(dxi, dyi);
1037 }
Owen Linf9a0a432011-08-17 22:07:43 +08001038 return true;
1039 }
1040
Chih-Chung Chang6b891c62012-06-07 20:09:13 +08001041 private int calculateDeltaY(float delta) {
1042 if (mTouchBoxDeletable) return (int) (delta + 0.5f);
1043
1044 // don't let items that can't be deleted be dragged more than
1045 // maxScrollDistance, and make it harder and harder to drag.
1046 int size = getHeight();
1047 float maxScrollDistance = 0.15f * size;
1048 if (Math.abs(delta) >= size) {
1049 delta = delta > 0 ? maxScrollDistance : -maxScrollDistance;
1050 } else {
1051 delta = maxScrollDistance *
1052 FloatMath.sin((delta / size) * (float) (Math.PI / 2));
1053 }
1054 return (int) (delta + 0.5f);
1055 }
1056
Owen Linf9a0a432011-08-17 22:07:43 +08001057 @Override
Chih-Chung Chang3a028092012-03-14 17:39:42 +08001058 public boolean onFling(float velocityX, float velocityY) {
Chih-Chung Chang65757942012-05-06 03:25:16 +08001059 if (mIgnoreSwipingGesture) return true;
Chih-Chung Change9494162012-09-06 20:02:09 +08001060 if (mModeChanged) return true;
Yuli Huang2ce3c3b2012-02-23 22:26:12 +08001061 if (swipeImages(velocityX, velocityY)) {
Chih-Chung Changb3aab902011-10-03 21:11:39 +08001062 mIgnoreUpEvent = true;
Chih-Chung Chang6b891c62012-06-07 20:09:13 +08001063 } else {
1064 flingImages(velocityX, velocityY);
Owen Linf9a0a432011-08-17 22:07:43 +08001065 }
Bobby Georgescu499ac9e2012-10-03 13:31:33 -07001066 mHadFling = true;
Owen Linf9a0a432011-08-17 22:07:43 +08001067 return true;
1068 }
1069
Chih-Chung Chang6b891c62012-06-07 20:09:13 +08001070 private boolean flingImages(float velocityX, float velocityY) {
1071 int vx = (int) (velocityX + 0.5f);
1072 int vy = (int) (velocityY + 0.5f);
1073 if (!mFilmMode) {
1074 return mPositionController.flingPage(vx, vy);
1075 }
1076 if (Math.abs(velocityX) > Math.abs(velocityY)) {
1077 return mPositionController.flingFilmX(vx);
1078 }
1079 // If we scrolled in Y direction fast enough, treat it as a delete
1080 // gesture.
1081 if (!mFilmMode || mTouchBoxIndex == Integer.MAX_VALUE
1082 || !mTouchBoxDeletable) {
1083 return false;
1084 }
Ahbong Chang78179792012-07-30 11:34:13 +08001085 int maxVelocity = GalleryUtils.dpToPixel(MAX_DISMISS_VELOCITY);
1086 int escapeVelocity = GalleryUtils.dpToPixel(SWIPE_ESCAPE_VELOCITY);
Chih-Chung Chang6b891c62012-06-07 20:09:13 +08001087 int centerY = mPositionController.getPosition(mTouchBoxIndex)
1088 .centerY();
1089 boolean fastEnough = (Math.abs(vy) > escapeVelocity)
1090 && (Math.abs(vy) > Math.abs(vx))
1091 && ((vy > 0) == (centerY > getHeight() / 2));
1092 if (fastEnough) {
1093 vy = Math.min(vy, maxVelocity);
1094 int duration = mPositionController.flingFilmY(mTouchBoxIndex, vy);
1095 if (duration >= 0) {
1096 mPositionController.setPopFromTop(vy < 0);
1097 deleteAfterAnimation(duration);
1098 // We reset mTouchBoxIndex, so up() won't check if Y
1099 // scrolled far enough to be a delete gesture.
1100 mTouchBoxIndex = Integer.MAX_VALUE;
1101 return true;
1102 }
1103 }
1104 return false;
1105 }
1106
1107 private void deleteAfterAnimation(int duration) {
1108 MediaItem item = mModel.getMediaItem(mTouchBoxIndex);
1109 if (item == null) return;
Chih-Chung Chang517e1bd2012-06-19 17:34:50 +08001110 mListener.onCommitDeleteImage();
1111 mUndoIndexHint = mModel.getCurrentIndex() + mTouchBoxIndex;
Chih-Chung Chang6b891c62012-06-07 20:09:13 +08001112 mHolding |= HOLD_DELETE;
1113 Message m = mHandler.obtainMessage(MSG_DELETE_ANIMATION_DONE);
1114 m.obj = item.getPath();
1115 m.arg1 = mTouchBoxIndex;
1116 mHandler.sendMessageDelayed(m, duration);
1117 }
1118
Owen Linf9a0a432011-08-17 22:07:43 +08001119 @Override
Chih-Chung Chang3a028092012-03-14 17:39:42 +08001120 public boolean onScaleBegin(float focusX, float focusY) {
Chih-Chung Chang65757942012-05-06 03:25:16 +08001121 if (mIgnoreSwipingGesture) return true;
Chih-Chung Chang33f85672012-05-01 15:35:02 +08001122 // We ignore the scaling gesture if it is a camera preview.
1123 mIgnoreScalingGesture = mPictures.get(0).isCamera();
1124 if (mIgnoreScalingGesture) {
1125 return true;
1126 }
Chih-Chung Chang3a028092012-03-14 17:39:42 +08001127 mPositionController.beginScale(focusX, focusY);
Chih-Chung Chang099989b2012-04-18 20:08:57 +08001128 // We can change mode if we are in film mode, or we are in page
1129 // mode and at minimal scale.
1130 mCanChangeMode = mFilmMode
1131 || mPositionController.isAtMinimalScale();
Chih-Chung Chang2ce59cb2012-06-18 17:24:58 +08001132 mAccScale = 1f;
Owen Linf9a0a432011-08-17 22:07:43 +08001133 return true;
1134 }
Owen Linf9a0a432011-08-17 22:07:43 +08001135
1136 @Override
Chih-Chung Chang3a028092012-03-14 17:39:42 +08001137 public boolean onScale(float focusX, float focusY, float scale) {
Chih-Chung Chang65757942012-05-06 03:25:16 +08001138 if (mIgnoreSwipingGesture) return true;
1139 if (mIgnoreScalingGesture) return true;
Chih-Chung Chang18958c52012-05-01 02:58:59 +08001140 if (mModeChanged) return true;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001141 if (Float.isNaN(scale) || Float.isInfinite(scale)) return false;
Chih-Chung Chang33f85672012-05-01 15:35:02 +08001142
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001143 int outOfRange = mPositionController.scaleBy(scale, focusX, focusY);
1144
Chih-Chung Chang2ce59cb2012-06-18 17:24:58 +08001145 // We wait for a large enough scale change before changing mode.
1146 // Otherwise we may mistakenly treat a zoom-in gesture as zoom-out
1147 // or vice versa.
1148 mAccScale *= scale;
1149 boolean largeEnough = (mAccScale < 0.97f || mAccScale > 1.03f);
1150
Chih-Chung Chang18958c52012-05-01 02:58:59 +08001151 // If mode changes, we treat this scaling gesture has ended.
Chih-Chung Chang2ce59cb2012-06-18 17:24:58 +08001152 if (mCanChangeMode && largeEnough) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001153 if ((outOfRange < 0 && !mFilmMode) ||
1154 (outOfRange > 0 && mFilmMode)) {
Chih-Chung Chang18958c52012-05-01 02:58:59 +08001155 stopExtraScalingIfNeeded();
1156
1157 // Removing the touch down flag allows snapback to happen
Chih-Chung Chang33f85672012-05-01 15:35:02 +08001158 // for film mode change.
Chih-Chung Chang18958c52012-05-01 02:58:59 +08001159 mHolding &= ~HOLD_TOUCH_DOWN;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001160 setFilmMode(!mFilmMode);
Chih-Chung Chang18958c52012-05-01 02:58:59 +08001161
1162 // We need to call onScaleEnd() before setting mModeChanged
1163 // to true.
1164 onScaleEnd();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001165 mModeChanged = true;
1166 return true;
Chih-Chung Chang534b12f2012-03-21 19:01:30 +08001167 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001168 }
1169
Chih-Chung Chang18958c52012-05-01 02:58:59 +08001170 if (outOfRange != 0) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001171 startExtraScalingIfNeeded();
Chih-Chung Chang534b12f2012-03-21 19:01:30 +08001172 } else {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001173 stopExtraScalingIfNeeded();
Chih-Chung Chang534b12f2012-03-21 19:01:30 +08001174 }
Owen Linf9a0a432011-08-17 22:07:43 +08001175 return true;
1176 }
1177
Chih-Chung Chang33f85672012-05-01 15:35:02 +08001178 @Override
1179 public void onScaleEnd() {
Chih-Chung Chang65757942012-05-06 03:25:16 +08001180 if (mIgnoreSwipingGesture) return;
1181 if (mIgnoreScalingGesture) return;
Chih-Chung Chang33f85672012-05-01 15:35:02 +08001182 if (mModeChanged) return;
1183 mPositionController.endScale();
1184 }
1185
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001186 private void startExtraScalingIfNeeded() {
1187 if (!mCancelExtraScalingPending) {
1188 mHandler.sendEmptyMessageDelayed(
1189 MSG_CANCEL_EXTRA_SCALING, 700);
1190 mPositionController.setExtraScalingRange(true);
1191 mCancelExtraScalingPending = true;
1192 }
1193 }
1194
1195 private void stopExtraScalingIfNeeded() {
1196 if (mCancelExtraScalingPending) {
1197 mHandler.removeMessages(MSG_CANCEL_EXTRA_SCALING);
1198 mPositionController.setExtraScalingRange(false);
1199 mCancelExtraScalingPending = false;
1200 }
1201 }
1202
Owen Linf9a0a432011-08-17 22:07:43 +08001203 @Override
Chih-Chung Chang6b891c62012-06-07 20:09:13 +08001204 public void onDown(float x, float y) {
Chih-Chung Chang517e1bd2012-06-19 17:34:50 +08001205 checkHideUndoBar(UNDO_BAR_TOUCHED);
1206
Chih-Chung Chang6b891c62012-06-07 20:09:13 +08001207 mDeltaY = 0;
Chih-Chung Change9494162012-09-06 20:02:09 +08001208 mModeChanged = false;
Chih-Chung Chang6b891c62012-06-07 20:09:13 +08001209
Chih-Chung Chang65757942012-05-06 03:25:16 +08001210 if (mIgnoreSwipingGesture) return;
1211
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001212 mHolding |= HOLD_TOUCH_DOWN;
Chih-Chung Chang17ffedd2012-05-03 18:22:59 +08001213
1214 if (mFilmMode && mPositionController.isScrolling()) {
1215 mDownInScrolling = true;
1216 mPositionController.stopScrolling();
1217 } else {
1218 mDownInScrolling = false;
1219 }
Bobby Georgescu499ac9e2012-10-03 13:31:33 -07001220 mHadFling = false;
Chih-Chung Chang6b891c62012-06-07 20:09:13 +08001221 mScrolledAfterDown = false;
1222 if (mFilmMode) {
1223 int xi = (int) (x + 0.5f);
1224 int yi = (int) (y + 0.5f);
1225 mTouchBoxIndex = mPositionController.hitTest(xi, yi);
1226 if (mTouchBoxIndex < mPrevBound || mTouchBoxIndex > mNextBound) {
1227 mTouchBoxIndex = Integer.MAX_VALUE;
1228 } else {
1229 mTouchBoxDeletable =
1230 mPictures.get(mTouchBoxIndex).isDeletable();
1231 }
1232 } else {
1233 mTouchBoxIndex = Integer.MAX_VALUE;
1234 }
Chih-Chung Chang3a028092012-03-14 17:39:42 +08001235 }
1236
1237 @Override
1238 public void onUp() {
Chih-Chung Chang65757942012-05-06 03:25:16 +08001239 if (mIgnoreSwipingGesture) return;
1240
Chih-Chung Chang18958c52012-05-01 02:58:59 +08001241 mHolding &= ~HOLD_TOUCH_DOWN;
Chih-Chung Chang3a028092012-03-14 17:39:42 +08001242 mEdgeView.onRelease();
1243
Chih-Chung Chang6b891c62012-06-07 20:09:13 +08001244 // If we scrolled in Y direction far enough, treat it as a delete
1245 // gesture.
1246 if (mFilmMode && mScrolledAfterDown && !mFirstScrollX
1247 && mTouchBoxIndex != Integer.MAX_VALUE) {
1248 Rect r = mPositionController.getPosition(mTouchBoxIndex);
1249 int h = getHeight();
1250 if (Math.abs(r.centerY() - h * 0.5f) > 0.4f * h) {
1251 int duration = mPositionController
1252 .flingFilmY(mTouchBoxIndex, 0);
1253 if (duration >= 0) {
1254 mPositionController.setPopFromTop(r.centerY() < h * 0.5f);
1255 deleteAfterAnimation(duration);
1256 }
1257 }
1258 }
1259
Chih-Chung Chang3a028092012-03-14 17:39:42 +08001260 if (mIgnoreUpEvent) {
1261 mIgnoreUpEvent = false;
1262 return;
1263 }
Bobby Georgescub1a51392012-10-04 11:15:43 -07001264
Bobby Georgescudc55d3b2012-10-09 15:59:40 -07001265 if (!(mFilmMode && !mHadFling && mFirstScrollX
1266 && snapToNeighborImage())) {
Bobby Georgescub1a51392012-10-04 11:15:43 -07001267 snapback();
Bobby Georgescu499ac9e2012-10-03 13:31:33 -07001268 }
Chih-Chung Chang3a028092012-03-14 17:39:42 +08001269 }
Chih-Chung Chang65757942012-05-06 03:25:16 +08001270
1271 public void setSwipingEnabled(boolean enabled) {
1272 mIgnoreSwipingGesture = !enabled;
1273 }
1274 }
1275
1276 public void setSwipingEnabled(boolean enabled) {
1277 mGestureListener.setSwipingEnabled(enabled);
Owen Linf9a0a432011-08-17 22:07:43 +08001278 }
1279
Bobby Georgescu00ccf352012-09-19 16:51:05 -07001280 private void updateActionBar() {
1281 boolean isCamera = mPictures.get(0).isCamera();
1282 if (isCamera && !mFilmMode) {
1283 // Move into camera in page mode, lock
Bobby Georgescu00ccf352012-09-19 16:51:05 -07001284 mListener.onActionBarAllowed(false);
1285 } else {
1286 mListener.onActionBarAllowed(true);
1287 if (mFilmMode) mListener.onActionBarWanted();
1288 }
1289 }
1290
Bobby Georgescu7eea4d32012-09-06 17:14:02 -07001291 public void setFilmMode(boolean enabled) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001292 if (mFilmMode == enabled) return;
1293 mFilmMode = enabled;
1294 mPositionController.setFilmMode(mFilmMode);
Chih-Chung Changb8be1e02012-04-17 20:35:14 +08001295 mModel.setNeedFullImage(!enabled);
Chih-Chung Chang6b891c62012-06-07 20:09:13 +08001296 mModel.setFocusHintDirection(
1297 mFilmMode ? Model.FOCUS_HINT_PREVIOUS : Model.FOCUS_HINT_NEXT);
Bobby Georgescu00ccf352012-09-19 16:51:05 -07001298 updateActionBar();
Bobby Georgescu7eea4d32012-09-06 17:14:02 -07001299 mListener.onFilmModeChanged(enabled);
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001300 }
1301
1302 public boolean getFilmMode() {
1303 return mFilmMode;
Owen Linf9a0a432011-08-17 22:07:43 +08001304 }
1305
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001306 ////////////////////////////////////////////////////////////////////////////
1307 // Framework events
1308 ////////////////////////////////////////////////////////////////////////////
1309
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001310 public void pause() {
1311 mPositionController.skipAnimation();
1312 mTileView.freeTextures();
1313 for (int i = -SCREEN_NAIL_MAX; i <= SCREEN_NAIL_MAX; i++) {
Chih-Chung Changb8be1e02012-04-17 20:35:14 +08001314 mPictures.get(i).setScreenNail(null);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001315 }
Chih-Chung Chang6118af92012-06-22 20:56:04 +08001316 hideUndoBar();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001317 }
1318
1319 public void resume() {
1320 mTileView.prepareTextures();
Bobby Georgescu3ed6ae22012-10-10 23:29:18 -07001321 mPositionController.skipToFinalPosition();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001322 }
1323
Chih-Chung Chang33f85672012-05-01 15:35:02 +08001324 // move to the camera preview and show controls after resume
1325 public void resetToFirstPicture() {
1326 mModel.moveTo(0);
1327 setFilmMode(false);
1328 }
1329
Chih-Chung Chang517e1bd2012-06-19 17:34:50 +08001330 ////////////////////////////////////////////////////////////////////////////
1331 // Undo Bar
1332 ////////////////////////////////////////////////////////////////////////////
1333
1334 private int mUndoBarState;
1335 private static final int UNDO_BAR_SHOW = 1;
1336 private static final int UNDO_BAR_TIMEOUT = 2;
1337 private static final int UNDO_BAR_TOUCHED = 4;
Chih-Chung Chang6118af92012-06-22 20:56:04 +08001338 private static final int UNDO_BAR_FULL_CAMERA = 8;
1339 private static final int UNDO_BAR_DELETE_LAST = 16;
Chih-Chung Chang517e1bd2012-06-19 17:34:50 +08001340
Chih-Chung Chang6118af92012-06-22 20:56:04 +08001341 // "deleteLast" means if the deletion is on the last remaining picture in
1342 // the album.
1343 private void showUndoBar(boolean deleteLast) {
1344 mHandler.removeMessages(MSG_UNDO_BAR_TIMEOUT);
Chih-Chung Chang517e1bd2012-06-19 17:34:50 +08001345 mUndoBarState = UNDO_BAR_SHOW;
Chih-Chung Chang6118af92012-06-22 20:56:04 +08001346 if(deleteLast) mUndoBarState |= UNDO_BAR_DELETE_LAST;
Chih-Chung Chang517e1bd2012-06-19 17:34:50 +08001347 mUndoBar.animateVisibility(GLView.VISIBLE);
Chih-Chung Chang6118af92012-06-22 20:56:04 +08001348 mHandler.sendEmptyMessageDelayed(MSG_UNDO_BAR_TIMEOUT, 3000);
Chih-Chung Chang517e1bd2012-06-19 17:34:50 +08001349 }
1350
Chih-Chung Chang6118af92012-06-22 20:56:04 +08001351 private void hideUndoBar() {
1352 mHandler.removeMessages(MSG_UNDO_BAR_TIMEOUT);
Chih-Chung Chang517e1bd2012-06-19 17:34:50 +08001353 mListener.onCommitDeleteImage();
1354 mUndoBar.animateVisibility(GLView.INVISIBLE);
1355 mUndoBarState = 0;
1356 mUndoIndexHint = Integer.MAX_VALUE;
1357 }
1358
Chih-Chung Chang6118af92012-06-22 20:56:04 +08001359 // Check if the one of the conditions for hiding the undo bar has been
1360 // met. The conditions are:
1361 //
1362 // 1. It has been three seconds since last showing, and (a) the user has
1363 // touched, or (b) the deleted picture is the last remaining picture in the
1364 // album.
1365 //
1366 // 2. The camera is shown in full screen.
Chih-Chung Chang517e1bd2012-06-19 17:34:50 +08001367 private void checkHideUndoBar(int addition) {
1368 mUndoBarState |= addition;
Chih-Chung Chang6118af92012-06-22 20:56:04 +08001369 if ((mUndoBarState & UNDO_BAR_SHOW) == 0) return;
1370 boolean timeout = (mUndoBarState & UNDO_BAR_TIMEOUT) != 0;
1371 boolean touched = (mUndoBarState & UNDO_BAR_TOUCHED) != 0;
1372 boolean fullCamera = (mUndoBarState & UNDO_BAR_FULL_CAMERA) != 0;
1373 boolean deleteLast = (mUndoBarState & UNDO_BAR_DELETE_LAST) != 0;
1374 if ((timeout && (touched || deleteLast)) || fullCamera) {
Chih-Chung Chang517e1bd2012-06-19 17:34:50 +08001375 hideUndoBar();
1376 }
Chih-Chung Chang6b891c62012-06-07 20:09:13 +08001377 }
1378
Bobby Georgescu639095c2012-10-08 13:37:41 -07001379 public boolean canUndo() {
1380 return (mUndoBarState & UNDO_BAR_SHOW) != 0;
Chih-Chung Chang6118af92012-06-22 20:56:04 +08001381 }
1382
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001383 ////////////////////////////////////////////////////////////////////////////
1384 // Rendering
1385 ////////////////////////////////////////////////////////////////////////////
1386
1387 @Override
1388 protected void render(GLCanvas canvas) {
Chih-Chung Chang9f44f352012-05-29 15:28:36 -07001389 // Check if the camera preview occupies the full screen.
1390 boolean full = !mFilmMode && mPictures.get(0).isCamera()
1391 && mPositionController.isCenter()
1392 && mPositionController.isAtMinimalScale();
1393 if (full != mFullScreenCamera) {
1394 mFullScreenCamera = full;
1395 mListener.onFullScreenChanged(full);
Chih-Chung Chang6118af92012-06-22 20:56:04 +08001396 if (full) mHandler.sendEmptyMessage(MSG_UNDO_BAR_FULL_CAMERA);
Chih-Chung Chang9f44f352012-05-29 15:28:36 -07001397 }
1398
1399 // Determine how many photos we need to draw in addition to the center
1400 // one.
1401 int neighbors;
1402 if (mFullScreenCamera) {
1403 neighbors = 0;
1404 } else {
1405 // In page mode, we draw only one previous/next photo. But if we are
1406 // doing capture animation, we want to draw all photos.
1407 boolean inPageMode = (mPositionController.getFilmRatio() == 0f);
1408 boolean inCaptureAnimation =
1409 ((mHolding & HOLD_CAPTURE_ANIMATION) != 0);
1410 if (inPageMode && !inCaptureAnimation) {
1411 neighbors = 1;
1412 } else {
1413 neighbors = SCREEN_NAIL_MAX;
1414 }
1415 }
Chih-Chung Changba12eae2012-05-07 02:29:37 +08001416
Chih-Chung Changc4791b72012-05-18 19:10:36 -07001417 // Draw photos from back to front
1418 for (int i = neighbors; i >= -neighbors; i--) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001419 Rect r = mPositionController.getPosition(i);
1420 mPictures.get(i).draw(canvas, r);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001421 }
1422
Chih-Chung Chang6b891c62012-06-07 20:09:13 +08001423 renderChild(canvas, mEdgeView);
1424 renderChild(canvas, mUndoBar);
1425
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001426 mPositionController.advanceAnimation();
1427 checkFocusSwitching();
1428 }
1429
1430 ////////////////////////////////////////////////////////////////////////////
1431 // Film mode focus switching
1432 ////////////////////////////////////////////////////////////////////////////
1433
1434 // Runs in GL thread.
1435 private void checkFocusSwitching() {
1436 if (!mFilmMode) return;
1437 if (mHandler.hasMessages(MSG_SWITCH_FOCUS)) return;
1438 if (switchPosition() != 0) {
1439 mHandler.sendEmptyMessage(MSG_SWITCH_FOCUS);
1440 }
1441 }
1442
1443 // Runs in main thread.
1444 private void switchFocus() {
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001445 if (mHolding != 0) return;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001446 switch (switchPosition()) {
1447 case -1:
1448 switchToPrevImage();
1449 break;
1450 case 1:
1451 switchToNextImage();
1452 break;
1453 }
1454 }
1455
1456 // Returns -1 if we should switch focus to the previous picture, +1 if we
1457 // should switch to the next, 0 otherwise.
1458 private int switchPosition() {
1459 Rect curr = mPositionController.getPosition(0);
1460 int center = getWidth() / 2;
1461
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001462 if (curr.left > center && mPrevBound < 0) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001463 Rect prev = mPositionController.getPosition(-1);
1464 int currDist = curr.left - center;
1465 int prevDist = center - prev.right;
1466 if (prevDist < currDist) {
1467 return -1;
1468 }
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001469 } else if (curr.right < center && mNextBound > 0) {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001470 Rect next = mPositionController.getPosition(1);
1471 int currDist = center - curr.right;
1472 int nextDist = next.left - center;
1473 if (nextDist < currDist) {
1474 return 1;
1475 }
1476 }
1477
1478 return 0;
1479 }
1480
Chih-Chung Chang17ffedd2012-05-03 18:22:59 +08001481 // Switch to the previous or next picture if the hit position is inside
1482 // one of their boxes. This runs in main thread.
1483 private void switchToHitPicture(int x, int y) {
1484 if (mPrevBound < 0) {
1485 Rect r = mPositionController.getPosition(-1);
1486 if (r.right >= x) {
1487 slideToPrevPicture();
1488 return;
1489 }
1490 }
1491
1492 if (mNextBound > 0) {
1493 Rect r = mPositionController.getPosition(1);
1494 if (r.left <= x) {
1495 slideToNextPicture();
1496 return;
1497 }
1498 }
1499 }
1500
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001501 ////////////////////////////////////////////////////////////////////////////
1502 // Page mode focus switching
1503 //
1504 // We slide image to the next one or the previous one in two cases: 1: If
1505 // the user did a fling gesture with enough velocity. 2 If the user has
1506 // moved the picture a lot.
1507 ////////////////////////////////////////////////////////////////////////////
1508
1509 private boolean swipeImages(float velocityX, float velocityY) {
1510 if (mFilmMode) return false;
1511
1512 // Avoid swiping images if we're possibly flinging to view the
1513 // zoomed in picture vertically.
1514 PositionController controller = mPositionController;
1515 boolean isMinimal = controller.isAtMinimalScale();
1516 int edges = controller.getImageAtEdges();
1517 if (!isMinimal && Math.abs(velocityY) > Math.abs(velocityX))
1518 if ((edges & PositionController.IMAGE_AT_TOP_EDGE) == 0
1519 || (edges & PositionController.IMAGE_AT_BOTTOM_EDGE) == 0)
1520 return false;
1521
1522 // If we are at the edge of the current photo and the sweeping velocity
1523 // exceeds the threshold, slide to the next / previous image.
1524 if (velocityX < -SWIPE_THRESHOLD && (isMinimal
1525 || (edges & PositionController.IMAGE_AT_RIGHT_EDGE) != 0)) {
1526 return slideToNextPicture();
1527 } else if (velocityX > SWIPE_THRESHOLD && (isMinimal
1528 || (edges & PositionController.IMAGE_AT_LEFT_EDGE) != 0)) {
1529 return slideToPrevPicture();
1530 }
1531
1532 return false;
1533 }
1534
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001535 private void snapback() {
Chih-Chung Chang6b891c62012-06-07 20:09:13 +08001536 if ((mHolding & ~HOLD_DELETE) != 0) return;
Bobby Georgescu499ac9e2012-10-03 13:31:33 -07001537 if (mFilmMode || !snapToNeighborImage()) {
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001538 mPositionController.snapback();
1539 }
1540 }
1541
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001542 private boolean snapToNeighborImage() {
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001543 Rect r = mPositionController.getPosition(0);
1544 int viewW = getWidth();
Doris Liucda93152012-09-05 19:51:58 -07001545 // Setting the move threshold proportional to the width of the view
1546 int moveThreshold = viewW / 5 ;
1547 int threshold = moveThreshold + gapToSide(r.width(), viewW);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001548
1549 // If we have moved the picture a lot, switching.
1550 if (viewW - r.right > threshold) {
1551 return slideToNextPicture();
1552 } else if (r.left > threshold) {
1553 return slideToPrevPicture();
1554 }
1555
1556 return false;
1557 }
1558
1559 private boolean slideToNextPicture() {
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001560 if (mNextBound <= 0) return false;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001561 switchToNextImage();
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001562 mPositionController.startHorizontalSlide();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001563 return true;
1564 }
1565
1566 private boolean slideToPrevPicture() {
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001567 if (mPrevBound >= 0) return false;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001568 switchToPrevImage();
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001569 mPositionController.startHorizontalSlide();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001570 return true;
1571 }
1572
1573 private static int gapToSide(int imageWidth, int viewWidth) {
1574 return Math.max(0, (viewWidth - imageWidth) / 2);
1575 }
1576
1577 ////////////////////////////////////////////////////////////////////////////
1578 // Focus switching
1579 ////////////////////////////////////////////////////////////////////////////
1580
Bobby Georgescu7eea4d32012-09-06 17:14:02 -07001581 public void switchToImage(int index) {
1582 mModel.moveTo(index);
1583 }
1584
Owen Linf9a0a432011-08-17 22:07:43 +08001585 private void switchToNextImage() {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001586 mModel.moveTo(mModel.getCurrentIndex() + 1);
Owen Linf9a0a432011-08-17 22:07:43 +08001587 }
1588
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001589 private void switchToPrevImage() {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001590 mModel.moveTo(mModel.getCurrentIndex() - 1);
Owen Linf9a0a432011-08-17 22:07:43 +08001591 }
1592
Chih-Chung Chang160e6d72012-04-25 11:50:08 +08001593 private void switchToFirstImage() {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001594 mModel.moveTo(0);
Chih-Chung Chang160e6d72012-04-25 11:50:08 +08001595 }
1596
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001597 ////////////////////////////////////////////////////////////////////////////
1598 // Opening Animation
1599 ////////////////////////////////////////////////////////////////////////////
1600
1601 public void setOpenAnimationRect(Rect rect) {
1602 mPositionController.setOpenAnimationRect(rect);
Chih-Chung Changec412542011-09-26 17:34:06 +08001603 }
1604
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001605 ////////////////////////////////////////////////////////////////////////////
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001606 // Capture Animation
1607 ////////////////////////////////////////////////////////////////////////////
1608
1609 public boolean switchWithCaptureAnimation(int offset) {
1610 GLRoot root = getGLRoot();
Bobby Georgescu14ce29a2012-07-25 10:20:19 -07001611 if(root == null) return false;
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001612 root.lockRenderThread();
1613 try {
1614 return switchWithCaptureAnimationLocked(offset);
1615 } finally {
1616 root.unlockRenderThread();
1617 }
1618 }
1619
1620 private boolean switchWithCaptureAnimationLocked(int offset) {
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001621 if (mHolding != 0) return true;
1622 if (offset == 1) {
1623 if (mNextBound <= 0) return false;
Chih-Chung Chang61f94712012-05-02 20:05:16 +08001624 // Temporary disable action bar until the capture animation is done.
1625 if (!mFilmMode) mListener.onActionBarAllowed(false);
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001626 switchToNextImage();
1627 mPositionController.startCaptureAnimationSlide(-1);
1628 } else if (offset == -1) {
1629 if (mPrevBound >= 0) return false;
Chih-Chung Changc4791b72012-05-18 19:10:36 -07001630 if (mFilmMode) setFilmMode(false);
1631
1632 // If we are too far away from the first image (so that we don't
1633 // have all the ScreenNails in-between), we go directly without
1634 // animation.
1635 if (mModel.getCurrentIndex() > SCREEN_NAIL_MAX) {
1636 switchToFirstImage();
Chih-Chung Chang42e1fed2012-05-30 16:29:18 -07001637 mPositionController.skipToFinalPosition();
Chih-Chung Changc4791b72012-05-18 19:10:36 -07001638 return true;
1639 }
1640
Chih-Chung Chang160e6d72012-04-25 11:50:08 +08001641 switchToFirstImage();
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001642 mPositionController.startCaptureAnimationSlide(1);
1643 } else {
1644 return false;
1645 }
1646 mHolding |= HOLD_CAPTURE_ANIMATION;
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001647 Message m = mHandler.obtainMessage(MSG_CAPTURE_ANIMATION_DONE, offset, 0);
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +08001648 mHandler.sendMessageDelayed(m, PositionController.CAPTURE_ANIMATION_TIME);
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001649 return true;
1650 }
1651
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001652 private void captureAnimationDone(int offset) {
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001653 mHolding &= ~HOLD_CAPTURE_ANIMATION;
Chih-Chung Change6251df2012-05-22 11:35:46 -07001654 if (offset == 1 && !mFilmMode) {
1655 // Now the capture animation is done, enable the action bar.
1656 mListener.onActionBarAllowed(true);
1657 mListener.onActionBarWanted();
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001658 }
Chih-Chung Chang2c617382012-04-20 20:06:19 +08001659 snapback();
1660 }
1661
1662 ////////////////////////////////////////////////////////////////////////////
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001663 // Card deck effect calculation
1664 ////////////////////////////////////////////////////////////////////////////
Chih-Chung Chang15b351a2012-03-15 16:38:45 +08001665
Chih-Chung Changcb4fb7c2012-03-21 16:19:21 +08001666 // Returns the scrolling progress value for an object moving out of a
1667 // view. The progress value measures how much the object has moving out of
1668 // the view. The object currently displays in [left, right), and the view is
1669 // at [0, viewWidth].
1670 //
1671 // The returned value is negative when the object is moving right, and
1672 // positive when the object is moving left. The value goes to -1 or 1 when
1673 // the object just moves out of the view completely. The value is 0 if the
1674 // object currently fills the view.
1675 private static float calculateMoveOutProgress(int left, int right,
1676 int viewWidth) {
1677 // w = object width
1678 // viewWidth = view width
1679 int w = right - left;
1680
1681 // If the object width is smaller than the view width,
1682 // |....view....|
1683 // |<-->| progress = -1 when left = viewWidth
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001684 // |<-->| progress = 0 when left = viewWidth / 2 - w / 2
Chih-Chung Changcb4fb7c2012-03-21 16:19:21 +08001685 // |<-->| progress = 1 when left = -w
Chih-Chung Changcb4fb7c2012-03-21 16:19:21 +08001686 if (w < viewWidth) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001687 int zx = viewWidth / 2 - w / 2;
1688 if (left > zx) {
1689 return -(left - zx) / (float) (viewWidth - zx); // progress = (0, -1]
1690 } else {
1691 return (left - zx) / (float) (-w - zx); // progress = [0, 1]
1692 }
Chih-Chung Changcb4fb7c2012-03-21 16:19:21 +08001693 }
1694
1695 // If the object width is larger than the view width,
1696 // |..view..|
1697 // |<--------->| progress = -1 when left = viewWidth
1698 // |<--------->| progress = 0 between left = 0
1699 // |<--------->| and right = viewWidth
1700 // |<--------->| progress = 1 when right = 0
1701 if (left > 0) {
1702 return -left / (float) viewWidth;
1703 }
1704
1705 if (right < viewWidth) {
1706 return (viewWidth - right) / (float) viewWidth;
1707 }
1708
1709 return 0;
1710 }
1711
1712 // Maps a scrolling progress value to the alpha factor in the fading
1713 // animation.
1714 private float getScrollAlpha(float scrollProgress) {
1715 return scrollProgress < 0 ? mAlphaInterpolator.getInterpolation(
1716 1 - Math.abs(scrollProgress)) : 1.0f;
1717 }
1718
1719 // Maps a scrolling progress value to the scaling factor in the fading
1720 // animation.
1721 private float getScrollScale(float scrollProgress) {
1722 float interpolatedProgress = mScaleInterpolator.getInterpolation(
1723 Math.abs(scrollProgress));
1724 float scale = (1 - interpolatedProgress) +
1725 interpolatedProgress * TRANSITION_SCALE_FACTOR;
1726 return scale;
1727 }
1728
1729
1730 // This interpolator emulates the rate at which the perceived scale of an
1731 // object changes as its distance from a camera increases. When this
1732 // interpolator is applied to a scale animation on a view, it evokes the
1733 // sense that the object is shrinking due to moving away from the camera.
1734 private static class ZInterpolator {
1735 private float focalLength;
1736
1737 public ZInterpolator(float foc) {
1738 focalLength = foc;
1739 }
1740
1741 public float getInterpolation(float input) {
1742 return (1.0f - focalLength / (focalLength + input)) /
1743 (1.0f - focalLength / (focalLength + 1.0f));
Owen Linf9a0a432011-08-17 22:07:43 +08001744 }
1745 }
1746
Chih-Chung Changba12eae2012-05-07 02:29:37 +08001747 // Returns an interpolated value for the page/film transition.
1748 // When ratio = 0, the result is from.
1749 // When ratio = 1, the result is to.
1750 private static float interpolate(float ratio, float from, float to) {
1751 return from + (to - from) * ratio * ratio;
1752 }
1753
Chih-Chung Chang6b891c62012-06-07 20:09:13 +08001754 // Returns the alpha factor in film mode if a picture is not in the center.
1755 // The 0.03 lower bound is to make the item always visible a bit.
1756 private float getOffsetAlpha(float offset) {
1757 offset /= 0.5f;
1758 float alpha = (offset > 0) ? (1 - offset) : (1 + offset);
1759 return Utils.clamp(alpha, 0.03f, 1f);
1760 }
1761
Chih-Chung Changb7ec5532012-04-03 12:21:16 +08001762 ////////////////////////////////////////////////////////////////////////////
1763 // Simple public utilities
1764 ////////////////////////////////////////////////////////////////////////////
Owen Linf9a0a432011-08-17 22:07:43 +08001765
Chih-Chung Changbd141b52012-04-26 10:10:49 +08001766 public void setListener(Listener listener) {
1767 mListener = listener;
Owen Linf9a0a432011-08-17 22:07:43 +08001768 }
Owen Lin616a70f2012-05-07 16:35:53 +08001769
1770 public Rect getPhotoRect(int index) {
1771 return mPositionController.getPosition(index);
1772 }
1773
Owen Lin616a70f2012-05-07 16:35:53 +08001774 public PhotoFallbackEffect buildFallbackEffect(GLView root, GLCanvas canvas) {
1775 Rect location = new Rect();
1776 Utils.assertTrue(root.getBoundsOf(this, location));
1777
1778 Rect fullRect = bounds();
1779 PhotoFallbackEffect effect = new PhotoFallbackEffect();
1780 for (int i = -SCREEN_NAIL_MAX; i <= SCREEN_NAIL_MAX; ++i) {
1781 MediaItem item = mModel.getMediaItem(i);
1782 if (item == null) continue;
1783 ScreenNail sc = mModel.getScreenNail(i);
Owen Lin49affdc2012-05-21 16:52:25 -07001784 if (!(sc instanceof BitmapScreenNail)
1785 || ((BitmapScreenNail) sc).isShowingPlaceholder()) continue;
1786
1787 // Now, sc is BitmapScreenNail and is not showing placeholder
Owen Lin616a70f2012-05-07 16:35:53 +08001788 Rect rect = new Rect(getPhotoRect(i));
1789 if (!Rect.intersects(fullRect, rect)) continue;
1790 rect.offset(location.left, location.top);
1791
Owen Lin38155c42012-08-08 18:47:33 +08001792 int width = sc.getWidth();
1793 int height = sc.getHeight();
1794
1795 int rotation = mModel.getImageRotation(i);
1796 RawTexture texture;
1797 if ((rotation % 180) == 0) {
1798 texture = new RawTexture(width, height, true);
1799 canvas.beginRenderTarget(texture);
1800 canvas.translate(width / 2f, height / 2f);
1801 } else {
1802 texture = new RawTexture(height, width, true);
1803 canvas.beginRenderTarget(texture);
1804 canvas.translate(height / 2f, width / 2f);
1805 }
1806
1807 canvas.rotate(rotation, 0, 0, 1);
1808 canvas.translate(-width / 2f, -height / 2f);
1809 sc.draw(canvas, 0, 0, width, height);
Owen Lin616a70f2012-05-07 16:35:53 +08001810 canvas.endRenderTarget();
1811 effect.addEntry(item.getPath(), rect, texture);
1812 }
1813 return effect;
1814 }
Owen Linf9a0a432011-08-17 22:07:43 +08001815}