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