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 DisplayItem(MediaItem item)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 getAnimatablesEntry()68 public DirectLinkedList.Entry<DisplayItem> getAnimatablesEntry() { 69 return mAnimatablesEntry; 70 } 71 rotateImageBy(float theta)72 public final void rotateImageBy(float theta) { 73 mImageTheta += theta; 74 } 75 set(Vector3f position, int stackIndex, boolean performTransition)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 getStackIndex()122 public int getStackIndex() { 123 return mStackId; 124 } 125 getThumbnailImage(Context context, MediaItemTexture.Config config)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 getScreennailImage(Context context)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 clearScreennailImage()156 public void clearScreennailImage() { 157 if (mScreennailImage != null) { 158 mScreennailImage = null; 159 mHiResImage = null; 160 } 161 } 162 clearHiResImage()163 public void clearHiResImage() { 164 mHiResImage = null; 165 } 166 clearThumbnail()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 */ isAnimating()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 */ update(float timeElapsedInSec)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 */ commit()204 public final void commit() { 205 mAnimatedPosition.set(mTargetPosition); 206 mAnimatedTheta = mTargetTheta; 207 mAnimatedImageTheta = mImageTheta; 208 } 209 setHasFocus(boolean hasFocus, boolean pushDown)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 setSingleOffset(boolean useOffset, boolean pushAway, float x, float y, float z, float spreadValue)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 setOffset(boolean useOffset, boolean pushDown, float span, float dx1, float dy1, float dx2, float dy2)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 getHasFocus()330 public final boolean getHasFocus() { 331 return mHasFocus; 332 } 333 getHiResImage(Context context)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 isAlive()344 public boolean isAlive() { 345 return mAlive; 346 } 347 getImageTheta()348 public float getImageTheta() { 349 return mImageTheta; 350 } 351 } 352