| /* |
| * Copyright (C) 2009 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.cooliris.media; |
| |
| import java.util.Random; |
| |
| import android.content.Context; |
| |
| import com.cooliris.app.App; |
| import com.cooliris.media.FloatUtils; |
| |
| /** |
| * A simple structure for a MediaItem that can be rendered. |
| */ |
| public final class DisplayItem { |
| private static final float STACK_SPACING = 0.2f; |
| private DirectLinkedList.Entry<DisplayItem> mAnimatablesEntry = new DirectLinkedList.Entry<DisplayItem>(this); |
| private static final Random random = new Random(); |
| private Vector3f mStacktopPosition = new Vector3f(-1.0f, -1.0f, -1.0f); |
| private Vector3f mJitteredPosition = new Vector3f(); |
| private boolean mHasFocus; |
| private Vector3f mTargetPosition = new Vector3f(); |
| private float mTargetTheta; |
| private float mImageTheta; |
| private int mStackId; |
| private MediaItemTexture mThumbnailImage = null; |
| private Texture mScreennailImage = null; |
| private UriTexture mHiResImage = null; |
| private float mConvergenceSpeed = 1.0f; |
| |
| public final MediaItem mItemRef; |
| public float mAnimatedTheta; |
| public float mAnimatedImageTheta; |
| public float mAnimatedPlaceholderFade = 0f; |
| public boolean mAlive; |
| public Vector3f mAnimatedPosition = new Vector3f(); |
| public int mCurrentSlotIndex; |
| private boolean mPerformingScale; |
| private float mSpan; |
| private float mSpanDirection; |
| private float mStartOffset; |
| private float mSpanSpeed; |
| private static final String TAG = "DisplayItem"; |
| |
| public DisplayItem(MediaItem item) { |
| mItemRef = item; |
| mAnimatedImageTheta = item.mRotation; |
| mImageTheta = item.mRotation; |
| if (item == null) |
| throw new UnsupportedOperationException("Cannot create a displayitem from a null MediaItem."); |
| mCurrentSlotIndex = Shared.INVALID; |
| } |
| |
| public DirectLinkedList.Entry<DisplayItem> getAnimatablesEntry() { |
| return mAnimatablesEntry; |
| } |
| |
| public final void rotateImageBy(float theta) { |
| mImageTheta += theta; |
| } |
| |
| public final void set(Vector3f position, int stackIndex, boolean performTransition) { |
| mConvergenceSpeed = 1.0f; |
| Vector3f animatedPosition = mAnimatedPosition; |
| Vector3f targetPosition = mTargetPosition; |
| int seed = stackIndex; |
| int randomSeed = stackIndex; |
| |
| if (seed > 3) { |
| seed = 3; |
| randomSeed = 0; |
| } |
| |
| if (!mAlive) { |
| animatedPosition.set(position); |
| animatedPosition.z = -3.0f + stackIndex * STACK_SPACING; |
| } |
| |
| targetPosition.set(position); |
| if (mStackId != stackIndex && stackIndex >= 0) { |
| mStackId = stackIndex; |
| } |
| |
| if (randomSeed == 0) { |
| if (stackIndex == 0) { |
| mTargetTheta = 0.0f; |
| } else if (mTargetTheta == 0.0f){ |
| mTargetTheta = 30.0f * (0.5f - (float) Math.random()); |
| } |
| mTargetPosition.z = seed * STACK_SPACING; |
| mJitteredPosition.set(0, 0, seed * STACK_SPACING); |
| } else { |
| int sign = (seed % 2 == 0) ? 1 : -1; |
| if (seed != 0 && !mStacktopPosition.equals(position) && mTargetTheta == 0) { |
| mTargetTheta = 30.0f * (0.5f - (float) Math.random()); |
| mJitteredPosition.x = sign * 12.0f * seed + (0.5f - random.nextFloat()) * 4 * seed; |
| mJitteredPosition.y = sign * 4 + ((sign == 1) ? -8.0f : sign * (random.nextFloat()) * 16.0f); |
| mJitteredPosition.x *= App.PIXEL_DENSITY; |
| mJitteredPosition.y *= App.PIXEL_DENSITY; |
| mJitteredPosition.z = seed * STACK_SPACING; |
| } |
| } |
| mTargetPosition.add(mJitteredPosition); |
| mStacktopPosition.set(position); |
| mStartOffset = 0.0f; |
| } |
| |
| public int getStackIndex() { |
| return mStackId; |
| } |
| |
| public Texture getThumbnailImage(Context context, MediaItemTexture.Config config) { |
| MediaItemTexture texture = mThumbnailImage; |
| if (texture == null && config != null) { |
| if (mItemRef.mId != Shared.INVALID) { |
| texture = new MediaItemTexture(context, config, mItemRef); |
| } |
| mThumbnailImage = texture; |
| } |
| return texture; |
| } |
| |
| public Texture getScreennailImage(Context context) { |
| Texture texture = mScreennailImage; |
| if (texture == null || texture.mState == Texture.STATE_ERROR) { |
| MediaSet parentMediaSet = mItemRef.mParentMediaSet; |
| if (parentMediaSet != null && parentMediaSet.mDataSource.getThumbnailCache() == LocalDataSource.sThumbnailCache) { |
| if (mItemRef.mId != Shared.INVALID && mItemRef.mId != 0) { |
| texture = new MediaItemTexture(context, null, mItemRef); |
| } else if (mItemRef.mContentUri != null) { |
| texture = new UriTexture(mItemRef.mContentUri); |
| } |
| } else { |
| texture = new UriTexture(mItemRef.mScreennailUri); |
| ((UriTexture) texture).setCacheId(Utils.Crc64Long(mItemRef.mFilePath)); |
| } |
| mScreennailImage = texture; |
| } |
| return texture; |
| } |
| |
| public void clearScreennailImage() { |
| if (mScreennailImage != null) { |
| mScreennailImage = null; |
| mHiResImage = null; |
| } |
| } |
| |
| public void clearHiResImage() { |
| mHiResImage = null; |
| } |
| |
| public void clearThumbnail() { |
| mThumbnailImage = null; |
| } |
| |
| /** |
| * Use this function to query the animation state of the display item |
| * |
| * @return true if the display item is animating |
| */ |
| public boolean isAnimating() { |
| return mAlive |
| && (mPerformingScale || |
| !mAnimatedPosition.equals(mTargetPosition) || mAnimatedTheta != mTargetTheta |
| || mAnimatedImageTheta != mImageTheta || mAnimatedPlaceholderFade != 1f); |
| } |
| |
| /** |
| * This function should be called every time the frame needs to be updated. |
| */ |
| public final void update(float timeElapsedInSec) { |
| if (mAlive) { |
| timeElapsedInSec *= 1.25f; |
| Vector3f animatedPosition = mAnimatedPosition; |
| Vector3f targetPosition = mTargetPosition; |
| timeElapsedInSec *= mConvergenceSpeed; |
| animatedPosition.x = FloatUtils.animate(animatedPosition.x, targetPosition.x, timeElapsedInSec); |
| animatedPosition.y = FloatUtils.animate(animatedPosition.y, targetPosition.y, timeElapsedInSec); |
| mAnimatedTheta = FloatUtils.animate(mAnimatedTheta, mTargetTheta, timeElapsedInSec); |
| mAnimatedImageTheta = FloatUtils.animate(mAnimatedImageTheta, mImageTheta, timeElapsedInSec); |
| mAnimatedPlaceholderFade = FloatUtils.animate(mAnimatedPlaceholderFade, 1f, timeElapsedInSec); |
| animatedPosition.z = FloatUtils.animate(animatedPosition.z, targetPosition.z, timeElapsedInSec); |
| } |
| } |
| |
| /** |
| * Commits all animations for the Display Item |
| */ |
| public final void commit() { |
| mAnimatedPosition.set(mTargetPosition); |
| mAnimatedTheta = mTargetTheta; |
| mAnimatedImageTheta = mImageTheta; |
| } |
| |
| public final void setHasFocus(boolean hasFocus, boolean pushDown) { |
| mConvergenceSpeed = 2.0f; |
| mHasFocus = hasFocus; |
| int seed = mStackId; |
| if (seed > 3) { |
| seed = 3; |
| } |
| if (hasFocus) { |
| mTargetPosition.set(mStacktopPosition); |
| mTargetPosition.add(mJitteredPosition); |
| mTargetPosition.add(mJitteredPosition); |
| mTargetPosition.z = seed * STACK_SPACING + (pushDown ? 1.0f : -0.5f); |
| } else { |
| mTargetPosition.set(mStacktopPosition); |
| mTargetPosition.add(mJitteredPosition); |
| mTargetPosition.z = seed * STACK_SPACING; |
| } |
| } |
| |
| public final void setSingleOffset(boolean useOffset, boolean pushAway, float x, float y, float z, float spreadValue) { |
| int seed = mStackId; |
| if (useOffset) { |
| mTargetPosition.set(mStacktopPosition); |
| if (spreadValue > 4.0f) |
| spreadValue = 4.0f + 0.1f * spreadValue; |
| if (spreadValue < 1.0f) { |
| spreadValue = 1.0f / spreadValue; |
| pushAway = true; |
| } |
| if (!pushAway) { |
| if (seed == 0) { |
| mTargetPosition.add(0, -spreadValue * 14, 0); |
| } |
| if (seed == 1) { |
| mTargetPosition.add(-spreadValue * 32, 0, 0); |
| } |
| if (seed == 2) { |
| mTargetPosition.add(0, spreadValue * 14, 0); |
| } |
| if (seed == 3) { |
| mTargetPosition.add(spreadValue * 32, 0, 0); |
| } |
| mTargetPosition.z = -1.0f * spreadValue + seed * STACK_SPACING * spreadValue; |
| mTargetTheta = 0.0f; |
| } else { |
| mTargetPosition.z = seed * STACK_SPACING + spreadValue * 0.5f; |
| } |
| } else { |
| if (seed > 3) { |
| seed = 3; |
| } |
| mTargetPosition.set(mStacktopPosition); |
| mTargetPosition.add(mJitteredPosition); |
| mTargetPosition.z = seed * STACK_SPACING; |
| if (seed != 0 && mTargetTheta == 0.0f) { |
| mTargetTheta = 30.0f * (0.5f - (float) Math.random()); |
| } |
| mStartOffset = 0.0f; |
| } |
| } |
| |
| public final void setOffset(boolean useOffset, boolean pushDown, float span, float dx1, float dy1, float dx2, float dy2) { |
| int seed = mStackId; |
| if (useOffset) { |
| mPerformingScale = true; |
| float spanDelta = span - mSpan; |
| float maxSlots = mItemRef.mParentMediaSet.getNumExpectedItems(); |
| maxSlots = FloatUtils.clamp(maxSlots, 0, GridLayer.MAX_ITEMS_PER_SLOT); |
| if (Math.abs(spanDelta) < 10 * App.PIXEL_DENSITY) { |
| // almost the same span |
| mStartOffset += (mSpanDirection * mSpanSpeed); |
| mStartOffset = FloatUtils.clamp(mStartOffset, 0, maxSlots); |
| } else { |
| mSpanSpeed = Math.abs(span / (600 * App.PIXEL_DENSITY)); |
| if (mSpanSpeed > 2.0f) { |
| mSpanSpeed = 2.0f; |
| } |
| mSpanSpeed *= 0.1f; |
| mSpanDirection = Math.signum(spanDelta); |
| } |
| mSpan = span; |
| mTargetPosition.set(mStacktopPosition); |
| if (!pushDown) { |
| if (maxSlots < 2) |
| return; |
| // If it is the stacktop, we track the top finger, ie, x1, y1 |
| // else |
| // we track bottom finger x2, y2 |
| // Instead of using linear interpolation, we will also try to |
| // look at the spread value to decide how many move at a given |
| // point of time. |
| int maxSeedVal = (int)(span / (125 * App.PIXEL_DENSITY)); |
| maxSeedVal = (int)FloatUtils.clamp(maxSeedVal, 2, maxSlots - 1); |
| float startOffset = FloatUtils.clamp(mStartOffset, 0, maxSlots - maxSeedVal - 1); |
| float offsetSeed = seed - startOffset; |
| float seedFactor = offsetSeed / maxSeedVal; |
| seedFactor = FloatUtils.clamp(seedFactor, 0.0f, 1.0f); |
| float dx = dx2 * seedFactor + (1.0f - seedFactor) * dx1; |
| float dy = dy2 * seedFactor + (1.0f - seedFactor) * dy1; |
| mTargetPosition.add(dx, dy, seed * 0.1f); |
| mTargetTheta = 0.0f; |
| } else { |
| mStartOffset = 0.0f; |
| mTargetPosition.z = seed * STACK_SPACING + 3.0f; |
| } |
| } else { |
| mPerformingScale = false; |
| mStartOffset = 0.0f; |
| if (seed > 3) { |
| seed = 3; |
| } |
| mTargetPosition.set(mStacktopPosition); |
| mTargetPosition.add(mJitteredPosition); |
| mTargetPosition.z = seed * STACK_SPACING; |
| if (seed != 0 && mTargetTheta == 0.0f) { |
| mTargetTheta = 30.0f * (0.5f - (float) Math.random()); |
| } |
| } |
| } |
| |
| public final boolean getHasFocus() { |
| return mHasFocus; |
| } |
| |
| public final Texture getHiResImage(Context context) { |
| UriTexture texture = mHiResImage; |
| if (texture == null) { |
| texture = new UriTexture(mItemRef.mContentUri); |
| texture.setCacheId(Utils.Crc64Long(mItemRef.mFilePath)); |
| mHiResImage = texture; |
| } |
| return texture; |
| } |
| |
| public boolean isAlive() { |
| return mAlive; |
| } |
| |
| public float getImageTheta() { |
| return mImageTheta; |
| } |
| } |