68a6c02febdefd5fcbeefbb9f82bed30d377c07a
[platform/framework/web/crosswalk.git] / src / content / public / android / java / src / org / chromium / content / browser / SnapScrollController.java
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
5 package org.chromium.content.browser;
6
7 import android.content.Context;
8 import android.util.DisplayMetrics;
9 import android.view.MotionEvent;
10
11 /**
12  * This objects controls the scroll snapping behavior based on scroll updates.
13  */
14 class SnapScrollController {
15     private static final String TAG = "SnapScrollController";
16     private static final int SNAP_NONE = 0;
17     private static final int SNAP_HORIZ = 1;
18     private static final int SNAP_VERT = 2;
19     private static final int SNAP_BOUND = 16;
20
21     private float mChannelDistance = 16f;
22     private int mSnapScrollMode = SNAP_NONE;
23     private int mFirstTouchX = -1;
24     private int mFirstTouchY = -1;
25     private float mDistanceX = 0;
26     private float mDistanceY = 0;
27     private ZoomManager mZoomManager;
28
29     SnapScrollController(Context context, ZoomManager zoomManager) {
30         calculateChannelDistance(context);
31         mZoomManager = zoomManager;
32     }
33
34     /**
35      * Updates the snap scroll mode based on the given X and Y distance to be moved on scroll.
36      * If the scroll update is above a threshold, the snapping behavior is reset.
37      * @param distanceX X distance for the current scroll update.
38      * @param distanceY Y distance for the current scroll update.
39      */
40     void updateSnapScrollMode(float distanceX, float distanceY) {
41         if (mSnapScrollMode == SNAP_HORIZ || mSnapScrollMode == SNAP_VERT) {
42             mDistanceX += Math.abs(distanceX);
43             mDistanceY += Math.abs(distanceY);
44             if (mSnapScrollMode == SNAP_HORIZ) {
45                 if (mDistanceY > mChannelDistance) {
46                     mSnapScrollMode = SNAP_NONE;
47                 } else if (mDistanceX > mChannelDistance) {
48                     mDistanceX = 0;
49                     mDistanceY = 0;
50                 }
51             } else {
52                 if (mDistanceX > mChannelDistance) {
53                     mSnapScrollMode = SNAP_NONE;
54                 } else if (mDistanceY > mChannelDistance) {
55                     mDistanceX = 0;
56                     mDistanceY = 0;
57                 }
58             }
59         }
60     }
61
62     /**
63      * Sets the snap scroll mode based on the event type.
64      * @param event The received MotionEvent.
65      */
66     void setSnapScrollingMode(MotionEvent event) {
67         switch(event.getAction()) {
68             case MotionEvent.ACTION_DOWN:
69                 mSnapScrollMode = SNAP_NONE;
70                 mFirstTouchX = (int) event.getX();
71                 mFirstTouchY = (int) event.getY();
72                 break;
73              // Set scrolling mode to SNAP_X if scroll towards x-axis exceeds SNAP_BOUND
74              // and movement towards y-axis is trivial.
75              // Set scrolling mode to SNAP_Y if scroll towards y-axis exceeds SNAP_BOUND
76              // and movement towards x-axis is trivial.
77              // Scrolling mode will remain in SNAP_NONE for other conditions.
78             case MotionEvent.ACTION_MOVE:
79                 if (!mZoomManager.isScaleGestureDetectionInProgress() &&
80                         mSnapScrollMode == SNAP_NONE) {
81                     int xDiff = (int) Math.abs(event.getX() - mFirstTouchX);
82                     int yDiff = (int) Math.abs(event.getY() - mFirstTouchY);
83                     if (xDiff > SNAP_BOUND && yDiff < SNAP_BOUND) {
84                         mSnapScrollMode = SNAP_HORIZ;
85                     } else if (xDiff < SNAP_BOUND && yDiff > SNAP_BOUND) {
86                         mSnapScrollMode = SNAP_VERT;
87                     }
88                 }
89                 break;
90             case MotionEvent.ACTION_UP:
91             case MotionEvent.ACTION_CANCEL:
92                 mFirstTouchX = -1;
93                 mFirstTouchY = -1;
94                 mDistanceX = 0;
95                 mDistanceY = 0;
96                 break;
97             default:
98                 break;
99         }
100     }
101
102     private void calculateChannelDistance(Context context) {
103         // The channel distance is adjusted for density and screen size.
104         final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
105         final double screenSize = Math.hypot((double) metrics.widthPixels / metrics.densityDpi,
106                 (double) metrics.heightPixels / metrics.densityDpi);
107         if (screenSize < 3.0) {
108             mChannelDistance = 16f;
109         } else if (screenSize < 5.0) {
110             mChannelDistance = 22f;
111         } else if (screenSize < 7.0) {
112             mChannelDistance = 28f;
113         } else {
114             mChannelDistance = 34f;
115         }
116         mChannelDistance = mChannelDistance * metrics.density;
117         if (mChannelDistance < 16f) mChannelDistance = 16f;
118     }
119
120     /**
121      * @return Whether current snap scroll mode is vertical.
122      */
123     boolean isSnapVertical() {
124         return mSnapScrollMode == SNAP_VERT;
125     }
126
127     /**
128      * @return Whether current snap scroll mode is horizontal.
129      */
130     boolean isSnapHorizontal() {
131         return mSnapScrollMode == SNAP_HORIZ;
132     }
133
134     /**
135      * @return Whether currently snapping scrolls.
136      */
137     boolean isSnappingScrolls() {
138         return mSnapScrollMode != SNAP_NONE;
139     }
140 }