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
17package com.cooliris.media;
18
19import java.util.Random;
20
21import android.content.Context;
22
23import com.cooliris.app.App;
24import com.cooliris.media.FloatUtils;
25
26/**
27 * A simple structure for a MediaItem that can be rendered.
28 */
29public 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