1 /* 2 * Copyright (C) 2009 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 17 package com.cooliris.media; 18 19 import java.util.Random; 20 21 import android.content.Context; 22 23 import com.cooliris.app.App; 24 import com.cooliris.media.FloatUtils; 25 26 /** 27 * A simple structure for a MediaItem that can be rendered. 28 */ 29 public final class DisplayItem { 30 private static final float STACK_SPACING = 0.2f; 31 private DirectLinkedList.Entry<DisplayItem> mAnimatablesEntry = new DirectLinkedList.Entry<DisplayItem>(this); 32 private static final Random random = new Random(); 33 private Vector3f mStacktopPosition = new Vector3f(-1.0f, -1.0f, -1.0f); 34 private Vector3f mJitteredPosition = new Vector3f(); 35 private boolean mHasFocus; 36 private Vector3f mTargetPosition = new Vector3f(); 37 private float mTargetTheta; 38 private float mImageTheta; 39 private int mStackId; 40 private MediaItemTexture mThumbnailImage = null; 41 private Texture mScreennailImage = null; 42 private UriTexture mHiResImage = null; 43 private float mConvergenceSpeed = 1.0f; 44 45 public final MediaItem mItemRef; 46 public float mAnimatedTheta; 47 public float mAnimatedImageTheta; 48 public float mAnimatedPlaceholderFade = 0f; 49 public boolean mAlive; 50 public Vector3f mAnimatedPosition = new Vector3f(); 51 public int mCurrentSlotIndex; 52 private boolean mPerformingScale; 53 private float mSpan; 54 private float mSpanDirection; 55 private float mStartOffset; 56 private float mSpanSpeed; 57 private static final String TAG = "DisplayItem"; 58 59 public DisplayItem(MediaItem item) { 60 mItemRef = item; 61 mAnimatedImageTheta = item.mRotation; 62 mImageTheta = item.mRotation; 63 if (item == null) 64 throw new UnsupportedOperationException("Cannot create a displayitem from a null MediaItem."); 65 mCurrentSlotIndex = Shared.INVALID; 66 } 67 68 public DirectLinkedList.Entry<DisplayItem> getAnimatablesEntry() { 69 return mAnimatablesEntry; 70 } 71 72 public final void rotateImageBy(float theta) { 73 mImageTheta += theta; 74 } 75 76 public final void set(Vector3f position, int stackIndex, boolean performTransition) { 77 mConvergenceSpeed = 1.0f; 78 Vector3f animatedPosition = mAnimatedPosition; 79 Vector3f targetPosition = mTargetPosition; 80 int seed = stackIndex; 81 int randomSeed = stackIndex; 82 83 if (seed > 3) { 84 seed = 3; 85 randomSeed = 0; 86 } 87 88 if (!mAlive) { 89 animatedPosition.set(position); 90 animatedPosition.z = -3.0f + stackIndex * STACK_SPACING; 91 } 92 93 targetPosition.set(position); 94 if (mStackId != stackIndex && stackIndex >= 0) { 95 mStackId = stackIndex; 96 } 97 98 if (randomSeed == 0) { 99 if (stackIndex == 0) { 100 mTargetTheta = 0.0f; 101 } else if (mTargetTheta == 0.0f){ 102 mTargetTheta = 30.0f * (0.5f - (float) Math.random()); 103 } 104 mTargetPosition.z = seed * STACK_SPACING; 105 mJitteredPosition.set(0, 0, seed * STACK_SPACING); 106 } else { 107 int sign = (seed % 2 == 0) ? 1 : -1; 108 if (seed != 0 && !mStacktopPosition.equals(position) && mTargetTheta == 0) { 109 mTargetTheta = 30.0f * (0.5f - (float) Math.random()); 110 mJitteredPosition.x = sign * 12.0f * seed + (0.5f - random.nextFloat()) * 4 * seed; 111 mJitteredPosition.y = sign * 4 + ((sign == 1) ? -8.0f : sign * (random.nextFloat()) * 16.0f); 112 mJitteredPosition.x *= App.PIXEL_DENSITY; 113 mJitteredPosition.y *= App.PIXEL_DENSITY; 114 mJitteredPosition.z = seed * STACK_SPACING; 115 } 116 } 117 mTargetPosition.add(mJitteredPosition); 118 mStacktopPosition.set(position); 119 mStartOffset = 0.0f; 120 } 121 122 public int getStackIndex() { 123 return mStackId; 124 } 125 126 public Texture getThumbnailImage(Context context, MediaItemTexture.Config config) { 127 MediaItemTexture texture = mThumbnailImage; 128 if (texture == null && config != null) { 129 if (mItemRef.mId != Shared.INVALID) { 130 texture = new MediaItemTexture(context, config, mItemRef); 131 } 132 mThumbnailImage = texture; 133 } 134 return texture; 135 } 136 137 public Texture getScreennailImage(Context context) { 138 Texture texture = mScreennailImage; 139 if (texture == null || texture.mState == Texture.STATE_ERROR) { 140 MediaSet parentMediaSet = mItemRef.mParentMediaSet; 141 if (parentMediaSet != null && parentMediaSet.mDataSource.getThumbnailCache() == LocalDataSource.sThumbnailCache) { 142 if (mItemRef.mId != Shared.INVALID && mItemRef.mId != 0) { 143 texture = new MediaItemTexture(context, null, mItemRef); 144 } else if (mItemRef.mContentUri != null) { 145 texture = new UriTexture(mItemRef.mContentUri); 146 } 147 } else { 148 texture = new UriTexture(mItemRef.mScreennailUri); 149 ((UriTexture) texture).setCacheId(Utils.Crc64Long(mItemRef.mFilePath)); 150 } 151 mScreennailImage = texture; 152 } 153 return texture; 154 } 155 156 public void clearScreennailImage() { 157 if (mScreennailImage != null) { 158 mScreennailImage = null; 159 mHiResImage = null; 160 } 161 } 162 163 public void clearHiResImage() { 164 mHiResImage = null; 165 } 166 167 public void clearThumbnail() { 168 mThumbnailImage = null; 169 } 170 171 /** 172 * Use this function to query the animation state of the display item 173 * 174 * @return true if the display item is animating 175 */ 176 public boolean isAnimating() { 177 return mAlive 178 && (mPerformingScale || 179 !mAnimatedPosition.equals(mTargetPosition) || mAnimatedTheta != mTargetTheta 180 || mAnimatedImageTheta != mImageTheta || mAnimatedPlaceholderFade != 1f); 181 } 182 183 /** 184 * This function should be called every time the frame needs to be updated. 185 */ 186 public final void update(float timeElapsedInSec) { 187 if (mAlive) { 188 timeElapsedInSec *= 1.25f; 189 Vector3f animatedPosition = mAnimatedPosition; 190 Vector3f targetPosition = mTargetPosition; 191 timeElapsedInSec *= mConvergenceSpeed; 192 animatedPosition.x = FloatUtils.animate(animatedPosition.x, targetPosition.x, timeElapsedInSec); 193 animatedPosition.y = FloatUtils.animate(animatedPosition.y, targetPosition.y, timeElapsedInSec); 194 mAnimatedTheta = FloatUtils.animate(mAnimatedTheta, mTargetTheta, timeElapsedInSec); 195 mAnimatedImageTheta = FloatUtils.animate(mAnimatedImageTheta, mImageTheta, timeElapsedInSec); 196 mAnimatedPlaceholderFade = FloatUtils.animate(mAnimatedPlaceholderFade, 1f, timeElapsedInSec); 197 animatedPosition.z = FloatUtils.animate(animatedPosition.z, targetPosition.z, timeElapsedInSec); 198 } 199 } 200 201 /** 202 * Commits all animations for the Display Item 203 */ 204 public final void commit() { 205 mAnimatedPosition.set(mTargetPosition); 206 mAnimatedTheta = mTargetTheta; 207 mAnimatedImageTheta = mImageTheta; 208 } 209 210 public final void setHasFocus(boolean hasFocus, boolean pushDown) { 211 mConvergenceSpeed = 2.0f; 212 mHasFocus = hasFocus; 213 int seed = mStackId; 214 if (seed > 3) { 215 seed = 3; 216 } 217 if (hasFocus) { 218 mTargetPosition.set(mStacktopPosition); 219 mTargetPosition.add(mJitteredPosition); 220 mTargetPosition.add(mJitteredPosition); 221 mTargetPosition.z = seed * STACK_SPACING + (pushDown ? 1.0f : -0.5f); 222 } else { 223 mTargetPosition.set(mStacktopPosition); 224 mTargetPosition.add(mJitteredPosition); 225 mTargetPosition.z = seed * STACK_SPACING; 226 } 227 } 228 229 public final void setSingleOffset(boolean useOffset, boolean pushAway, float x, float y, float z, float spreadValue) { 230 int seed = mStackId; 231 if (useOffset) { 232 mTargetPosition.set(mStacktopPosition); 233 if (spreadValue > 4.0f) 234 spreadValue = 4.0f + 0.1f * spreadValue; 235 if (spreadValue < 1.0f) { 236 spreadValue = 1.0f / spreadValue; 237 pushAway = true; 238 } 239 if (!pushAway) { 240 if (seed == 0) { 241 mTargetPosition.add(0, -spreadValue * 14, 0); 242 } 243 if (seed == 1) { 244 mTargetPosition.add(-spreadValue * 32, 0, 0); 245 } 246 if (seed == 2) { 247 mTargetPosition.add(0, spreadValue * 14, 0); 248 } 249 if (seed == 3) { 250 mTargetPosition.add(spreadValue * 32, 0, 0); 251 } 252 mTargetPosition.z = -1.0f * spreadValue + seed * STACK_SPACING * spreadValue; 253 mTargetTheta = 0.0f; 254 } else { 255 mTargetPosition.z = seed * STACK_SPACING + spreadValue * 0.5f; 256 } 257 } else { 258 if (seed > 3) { 259 seed = 3; 260 } 261 mTargetPosition.set(mStacktopPosition); 262 mTargetPosition.add(mJitteredPosition); 263 mTargetPosition.z = seed * STACK_SPACING; 264 if (seed != 0 && mTargetTheta == 0.0f) { 265 mTargetTheta = 30.0f * (0.5f - (float) Math.random()); 266 } 267 mStartOffset = 0.0f; 268 } 269 } 270 271 public final void setOffset(boolean useOffset, boolean pushDown, float span, float dx1, float dy1, float dx2, float dy2) { 272 int seed = mStackId; 273 if (useOffset) { 274 mPerformingScale = true; 275 float spanDelta = span - mSpan; 276 float maxSlots = mItemRef.mParentMediaSet.getNumExpectedItems(); 277 maxSlots = FloatUtils.clamp(maxSlots, 0, GridLayer.MAX_ITEMS_PER_SLOT); 278 if (Math.abs(spanDelta) < 10 * App.PIXEL_DENSITY) { 279 // almost the same span 280 mStartOffset += (mSpanDirection * mSpanSpeed); 281 mStartOffset = FloatUtils.clamp(mStartOffset, 0, maxSlots); 282 } else { 283 mSpanSpeed = Math.abs(span / (600 * App.PIXEL_DENSITY)); 284 if (mSpanSpeed > 2.0f) { 285 mSpanSpeed = 2.0f; 286 } 287 mSpanSpeed *= 0.1f; 288 mSpanDirection = Math.signum(spanDelta); 289 } 290 mSpan = span; 291 mTargetPosition.set(mStacktopPosition); 292 if (!pushDown) { 293 if (maxSlots < 2) 294 return; 295 // If it is the stacktop, we track the top finger, ie, x1, y1 296 // else 297 // we track bottom finger x2, y2 298 // Instead of using linear interpolation, we will also try to 299 // look at the spread value to decide how many move at a given 300 // point of time. 301 int maxSeedVal = (int)(span / (125 * App.PIXEL_DENSITY)); 302 maxSeedVal = (int)FloatUtils.clamp(maxSeedVal, 2, maxSlots - 1); 303 float startOffset = FloatUtils.clamp(mStartOffset, 0, maxSlots - maxSeedVal - 1); 304 float offsetSeed = seed - startOffset; 305 float seedFactor = offsetSeed / maxSeedVal; 306 seedFactor = FloatUtils.clamp(seedFactor, 0.0f, 1.0f); 307 float dx = dx2 * seedFactor + (1.0f - seedFactor) * dx1; 308 float dy = dy2 * seedFactor + (1.0f - seedFactor) * dy1; 309 mTargetPosition.add(dx, dy, seed * 0.1f); 310 mTargetTheta = 0.0f; 311 } else { 312 mStartOffset = 0.0f; 313 mTargetPosition.z = seed * STACK_SPACING + 3.0f; 314 } 315 } else { 316 mPerformingScale = false; 317 mStartOffset = 0.0f; 318 if (seed > 3) { 319 seed = 3; 320 } 321 mTargetPosition.set(mStacktopPosition); 322 mTargetPosition.add(mJitteredPosition); 323 mTargetPosition.z = seed * STACK_SPACING; 324 if (seed != 0 && mTargetTheta == 0.0f) { 325 mTargetTheta = 30.0f * (0.5f - (float) Math.random()); 326 } 327 } 328 } 329 330 public final boolean getHasFocus() { 331 return mHasFocus; 332 } 333 334 public final Texture getHiResImage(Context context) { 335 UriTexture texture = mHiResImage; 336 if (texture == null) { 337 texture = new UriTexture(mItemRef.mContentUri); 338 texture.setCacheId(Utils.Crc64Long(mItemRef.mFilePath)); 339 mHiResImage = texture; 340 } 341 return texture; 342 } 343 344 public boolean isAlive() { 345 return mAlive; 346 } 347 348 public float getImageTheta() { 349 return mImageTheta; 350 } 351 } 352