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 */
16package android.webkit;
17
18import com.android.internal.R;
19
20import android.content.Context;
21import android.content.res.Resources;
22import android.graphics.Canvas;
23import android.graphics.drawable.Drawable;
24import android.view.View;
25import android.widget.EdgeEffect;
26
27/**
28 * This class manages the edge glow effect when a WebView is flung or pulled beyond the edges.
29 * @hide
30 */
31public class OverScrollGlow {
32    private WebViewClassic mHostView;
33
34    private EdgeEffect mEdgeGlowTop;
35    private EdgeEffect mEdgeGlowBottom;
36    private EdgeEffect mEdgeGlowLeft;
37    private EdgeEffect mEdgeGlowRight;
38
39    private int mOverScrollDeltaX;
40    private int mOverScrollDeltaY;
41
42    public OverScrollGlow(WebViewClassic host) {
43        mHostView = host;
44        Context context = host.getContext();
45        mEdgeGlowTop = new EdgeEffect(context);
46        mEdgeGlowBottom = new EdgeEffect(context);
47        mEdgeGlowLeft = new EdgeEffect(context);
48        mEdgeGlowRight = new EdgeEffect(context);
49    }
50
51    /**
52     * Pull leftover touch scroll distance into one of the edge glows as appropriate.
53     *
54     * @param x Current X scroll offset
55     * @param y Current Y scroll offset
56     * @param oldX Old X scroll offset
57     * @param oldY Old Y scroll offset
58     * @param maxX Maximum range for horizontal scrolling
59     * @param maxY Maximum range for vertical scrolling
60     */
61    public void pullGlow(int x, int y, int oldX, int oldY, int maxX, int maxY) {
62        // Only show overscroll bars if there was no movement in any direction
63        // as a result of scrolling.
64        if (oldX == mHostView.getScrollX() && oldY == mHostView.getScrollY()) {
65            // Don't show left/right glows if we fit the whole content.
66            // Also don't show if there was vertical movement.
67            if (maxX > 0) {
68                final int pulledToX = oldX + mOverScrollDeltaX;
69                if (pulledToX < 0) {
70                    mEdgeGlowLeft.onPull((float) mOverScrollDeltaX / mHostView.getWidth());
71                    if (!mEdgeGlowRight.isFinished()) {
72                        mEdgeGlowRight.onRelease();
73                    }
74                } else if (pulledToX > maxX) {
75                    mEdgeGlowRight.onPull((float) mOverScrollDeltaX / mHostView.getWidth());
76                    if (!mEdgeGlowLeft.isFinished()) {
77                        mEdgeGlowLeft.onRelease();
78                    }
79                }
80                mOverScrollDeltaX = 0;
81            }
82
83            if (maxY > 0 || mHostView.getWebView().getOverScrollMode() == View.OVER_SCROLL_ALWAYS) {
84                final int pulledToY = oldY + mOverScrollDeltaY;
85                if (pulledToY < 0) {
86                    mEdgeGlowTop.onPull((float) mOverScrollDeltaY / mHostView.getHeight());
87                    if (!mEdgeGlowBottom.isFinished()) {
88                        mEdgeGlowBottom.onRelease();
89                    }
90                } else if (pulledToY > maxY) {
91                    mEdgeGlowBottom.onPull((float) mOverScrollDeltaY / mHostView.getHeight());
92                    if (!mEdgeGlowTop.isFinished()) {
93                        mEdgeGlowTop.onRelease();
94                    }
95                }
96                mOverScrollDeltaY = 0;
97            }
98        }
99    }
100
101    /**
102     * Set touch delta values indicating the current amount of overscroll.
103     *
104     * @param deltaX
105     * @param deltaY
106     */
107    public void setOverScrollDeltas(int deltaX, int deltaY) {
108        mOverScrollDeltaX = deltaX;
109        mOverScrollDeltaY = deltaY;
110    }
111
112    /**
113     * Absorb leftover fling velocity into one of the edge glows as appropriate.
114     *
115     * @param x Current X scroll offset
116     * @param y Current Y scroll offset
117     * @param oldX Old X scroll offset
118     * @param oldY Old Y scroll offset
119     * @param rangeX Maximum range for horizontal scrolling
120     * @param rangeY Maximum range for vertical scrolling
121     */
122    public void absorbGlow(int x, int y, int oldX, int oldY, int rangeX, int rangeY) {
123        if (rangeY > 0 || mHostView.getWebView().getOverScrollMode() == View.OVER_SCROLL_ALWAYS) {
124            if (y < 0 && oldY >= 0) {
125                mEdgeGlowTop.onAbsorb((int) mHostView.mScroller.getCurrVelocity());
126                if (!mEdgeGlowBottom.isFinished()) {
127                    mEdgeGlowBottom.onRelease();
128                }
129            } else if (y > rangeY && oldY <= rangeY) {
130                mEdgeGlowBottom.onAbsorb((int) mHostView.mScroller.getCurrVelocity());
131                if (!mEdgeGlowTop.isFinished()) {
132                    mEdgeGlowTop.onRelease();
133                }
134            }
135        }
136
137        if (rangeX > 0) {
138            if (x < 0 && oldX >= 0) {
139                mEdgeGlowLeft.onAbsorb((int) mHostView.mScroller.getCurrVelocity());
140                if (!mEdgeGlowRight.isFinished()) {
141                    mEdgeGlowRight.onRelease();
142                }
143            } else if (x > rangeX && oldX <= rangeX) {
144                mEdgeGlowRight.onAbsorb((int) mHostView.mScroller.getCurrVelocity());
145                if (!mEdgeGlowLeft.isFinished()) {
146                    mEdgeGlowLeft.onRelease();
147                }
148            }
149        }
150    }
151
152    /**
153     * Draw the glow effect along the sides of the widget. mEdgeGlow* must be non-null.
154     *
155     * @param canvas Canvas to draw into, transformed into view coordinates.
156     * @return true if glow effects are still animating and the view should invalidate again.
157     */
158    public boolean drawEdgeGlows(Canvas canvas) {
159        final int scrollX = mHostView.getScrollX();
160        final int scrollY = mHostView.getScrollY();
161        final int width = mHostView.getWidth();
162        int height = mHostView.getHeight();
163
164        boolean invalidateForGlow = false;
165        if (!mEdgeGlowTop.isFinished()) {
166            final int restoreCount = canvas.save();
167
168            canvas.translate(scrollX, mHostView.getVisibleTitleHeight() + Math.min(0, scrollY));
169            mEdgeGlowTop.setSize(width, height);
170            invalidateForGlow |= mEdgeGlowTop.draw(canvas);
171            canvas.restoreToCount(restoreCount);
172        }
173        if (!mEdgeGlowBottom.isFinished()) {
174            final int restoreCount = canvas.save();
175
176            canvas.translate(-width + scrollX, Math.max(mHostView.computeMaxScrollY(), scrollY)
177                    + height);
178            canvas.rotate(180, width, 0);
179            mEdgeGlowBottom.setSize(width, height);
180            invalidateForGlow |= mEdgeGlowBottom.draw(canvas);
181            canvas.restoreToCount(restoreCount);
182        }
183        if (!mEdgeGlowLeft.isFinished()) {
184            final int restoreCount = canvas.save();
185
186            canvas.rotate(270);
187            canvas.translate(-height - scrollY, Math.min(0, scrollX));
188            mEdgeGlowLeft.setSize(height, width);
189            invalidateForGlow |= mEdgeGlowLeft.draw(canvas);
190            canvas.restoreToCount(restoreCount);
191        }
192        if (!mEdgeGlowRight.isFinished()) {
193            final int restoreCount = canvas.save();
194
195            canvas.rotate(90);
196            canvas.translate(scrollY,
197                    -(Math.max(mHostView.computeMaxScrollX(), scrollX) + width));
198            mEdgeGlowRight.setSize(height, width);
199            invalidateForGlow |= mEdgeGlowRight.draw(canvas);
200            canvas.restoreToCount(restoreCount);
201        }
202        return invalidateForGlow;
203    }
204
205    /**
206     * @return True if any glow is still animating
207     */
208    public boolean isAnimating() {
209        return (!mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished() ||
210                !mEdgeGlowLeft.isFinished() || !mEdgeGlowRight.isFinished());
211    }
212
213    /**
214     * Release all glows from any touch pulls in progress.
215     */
216    public void releaseAll() {
217        mEdgeGlowTop.onRelease();
218        mEdgeGlowBottom.onRelease();
219        mEdgeGlowLeft.onRelease();
220        mEdgeGlowRight.onRelease();
221    }
222}
223