1/*
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
19import android.graphics.Rect;
20import android.opengl.Matrix;
21import android.view.animation.DecelerateInterpolator;
22import android.view.animation.Interpolator;
23
24import com.android.gallery3d.common.Utils;
25
26// This class does the overscroll effect.
27class Paper {
28    @SuppressWarnings("unused")
29    private static final String TAG = "Paper";
30    private static final int ROTATE_FACTOR = 4;
31    private EdgeAnimation mAnimationLeft = new EdgeAnimation();
32    private EdgeAnimation mAnimationRight = new EdgeAnimation();
33    private int mWidth;
34    private float[] mMatrix = new float[16];
35
36    public void overScroll(float distance) {
37        distance /= mWidth;  // make it relative to width
38        if (distance < 0) {
39            mAnimationLeft.onPull(-distance);
40        } else {
41            mAnimationRight.onPull(distance);
42        }
43    }
44
45    public void edgeReached(float velocity) {
46        velocity /= mWidth;  // make it relative to width
47        if (velocity < 0) {
48            mAnimationRight.onAbsorb(-velocity);
49        } else {
50            mAnimationLeft.onAbsorb(velocity);
51        }
52    }
53
54    public void onRelease() {
55        mAnimationLeft.onRelease();
56        mAnimationRight.onRelease();
57    }
58
59    public boolean advanceAnimation() {
60        // Note that we use "|" because we want both animations get updated.
61        return mAnimationLeft.update() | mAnimationRight.update();
62    }
63
64    public void setSize(int width, int height) {
65        mWidth = width;
66    }
67
68    public float[] getTransform(Rect rect, float scrollX) {
69        float left = mAnimationLeft.getValue();
70        float right = mAnimationRight.getValue();
71        float screenX = rect.centerX() - scrollX;
72        // We linearly interpolate the value [left, right] for the screenX
73        // range int [-1/4, 5/4]*mWidth. So if part of the thumbnail is outside
74        // the screen, we still get some transform.
75        float x = screenX + mWidth / 4;
76        int range = 3 * mWidth / 2;
77        float t = ((range - x) * left - x * right) / range;
78        // compress t to the range (-1, 1) by the function
79        // f(t) = (1 / (1 + e^-t) - 0.5) * 2
80        // then multiply by 90 to make the range (-45, 45)
81        float degrees =
82                (1 / (1 + (float) Math.exp(-t * ROTATE_FACTOR)) - 0.5f) * 2 * -45;
83        Matrix.setIdentityM(mMatrix, 0);
84        Matrix.translateM(mMatrix, 0, mMatrix, 0, rect.centerX(), rect.centerY(), 0);
85        Matrix.rotateM(mMatrix, 0, degrees, 0, 1, 0);
86        Matrix.translateM(mMatrix, 0, mMatrix, 0, -rect.width() / 2, -rect.height() / 2, 0);
87        return mMatrix;
88    }
89}
90
91// This class follows the structure of frameworks's EdgeEffect class.
92class EdgeAnimation {
93    @SuppressWarnings("unused")
94    private static final String TAG = "EdgeAnimation";
95
96    private static final int STATE_IDLE = 0;
97    private static final int STATE_PULL = 1;
98    private static final int STATE_ABSORB = 2;
99    private static final int STATE_RELEASE = 3;
100
101    // Time it will take the effect to fully done in ms
102    private static final int ABSORB_TIME = 200;
103    private static final int RELEASE_TIME = 500;
104
105    private static final float VELOCITY_FACTOR = 0.1f;
106
107    private final Interpolator mInterpolator;
108
109    private int mState;
110    private float mValue;
111
112    private float mValueStart;
113    private float mValueFinish;
114    private long mStartTime;
115    private long mDuration;
116
117    public EdgeAnimation() {
118        mInterpolator = new DecelerateInterpolator();
119        mState = STATE_IDLE;
120    }
121
122    private void startAnimation(float start, float finish, long duration,
123            int newState) {
124        mValueStart = start;
125        mValueFinish = finish;
126        mDuration = duration;
127        mStartTime = now();
128        mState = newState;
129    }
130
131    // The deltaDistance's magnitude is in the range of -1 (no change) to 1.
132    // The value 1 is the full length of the view. Negative values means the
133    // movement is in the opposite direction.
134    public void onPull(float deltaDistance) {
135        if (mState == STATE_ABSORB) return;
136        mValue = Utils.clamp(mValue + deltaDistance, -1.0f, 1.0f);
137        mState = STATE_PULL;
138    }
139
140    public void onRelease() {
141        if (mState == STATE_IDLE || mState == STATE_ABSORB) return;
142        startAnimation(mValue, 0, RELEASE_TIME, STATE_RELEASE);
143    }
144
145    public void onAbsorb(float velocity) {
146        float finish = Utils.clamp(mValue + velocity * VELOCITY_FACTOR,
147                -1.0f, 1.0f);
148        startAnimation(mValue, finish, ABSORB_TIME, STATE_ABSORB);
149    }
150
151    public boolean update() {
152        if (mState == STATE_IDLE) return false;
153        if (mState == STATE_PULL) return true;
154
155        float t = Utils.clamp((float)(now() - mStartTime) / mDuration, 0.0f, 1.0f);
156        /* Use linear interpolation for absorb, quadratic for others */
157        float interp = (mState == STATE_ABSORB)
158                ? t : mInterpolator.getInterpolation(t);
159
160        mValue = mValueStart + (mValueFinish - mValueStart) * interp;
161
162        if (t >= 1.0f) {
163            switch (mState) {
164                case STATE_ABSORB:
165                    startAnimation(mValue, 0, RELEASE_TIME, STATE_RELEASE);
166                    break;
167                case STATE_RELEASE:
168                    mState = STATE_IDLE;
169                    break;
170            }
171        }
172
173        return true;
174    }
175
176    public float getValue() {
177        return mValue;
178    }
179
180    private long now() {
181        return AnimationTime.get();
182    }
183}
184