View Javadoc
1   /*
2    * Copyright 2012 Daniel Kurka
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5    * in compliance with the License. You may obtain a copy of the License at
6    * 
7    * http://www.apache.org/licenses/LICENSE-2.0
8    * 
9    * Unless required by applicable law or agreed to in writing, software distributed under the License
10   * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11   * or implied. See the License for the specific language governing permissions and limitations under
12   * the License.
13   */
14  package com.googlecode.mgwt.dom.client.recognizer.swipe;
15  
16  import com.google.gwt.core.client.GWT;
17  import com.google.gwt.event.shared.HasHandlers;
18  import com.googlecode.mgwt.dom.client.event.touch.Touch;
19  import com.googlecode.mgwt.dom.client.event.touch.TouchCancelEvent;
20  import com.googlecode.mgwt.dom.client.event.touch.TouchEndEvent;
21  import com.googlecode.mgwt.dom.client.event.touch.TouchHandler;
22  import com.googlecode.mgwt.dom.client.event.touch.TouchMoveEvent;
23  import com.googlecode.mgwt.dom.client.event.touch.TouchStartEvent;
24  import com.googlecode.mgwt.dom.client.recognizer.EventPropagator;
25  import com.googlecode.mgwt.dom.client.recognizer.swipe.SwipeEvent.DIRECTION;
26  
27  public class SwipeRecognizer implements TouchHandler {
28  
29    private static EventPropagator DEFAULT_EVENT_PROPAGATOR;
30  
31    private final HasHandlers source;
32  
33    private EventPropagator eventPropagator;
34  
35    private final int minDistance;
36  
37    private final int threshold;
38  
39    private int touchCount;
40  
41    private enum State {
42      INVALID, READY, FINDER_DOWN, FOUND_DIRECTION
43    }
44  
45    private State state;
46  
47    private DIRECTION direction;
48  
49    private int lastDistance;
50  
51    private int x;
52  
53    private int y;
54  
55    /**
56     * construct a swipe recognizer
57     * 
58     * @param source the source to fire events on
59     */
60    public SwipeRecognizer(HasHandlers source) {
61      this(source, 40);
62    }
63  
64    /**
65     * construct a swipe recognizer
66     * 
67     * @param source the source to fire events on
68     * @param minDistance the minimum distance to cover before this counts as a swipe
69     */
70    public SwipeRecognizer(HasHandlers source, int minDistance) {
71      this(source, minDistance, 10);
72    }
73  
74    /**
75     * construct a swipe recognizer
76     * 
77     * @param source the source to fire events on
78     * @param minDistance the minimum distance to cover before this counts as a swipe
79     * @param threshold the initial threshold before swipe start is fired
80     */
81    public SwipeRecognizer(HasHandlers source, int minDistance, int threshold) {
82      if (source == null)
83        throw new IllegalArgumentException("source can not be null");
84  
85      if (minDistance <= 0 || minDistance < threshold) {
86        throw new IllegalArgumentException("minDistance > 0 and minDistance > threshold");
87      }
88  
89      if (threshold <= 0) {
90        throw new IllegalArgumentException("threshold > 0");
91      }
92  
93      this.source = source;
94      this.minDistance = minDistance;
95      this.threshold = threshold;
96      this.touchCount = 0;
97      state = State.READY;
98    }
99  
100   /*
101    * (non-Javadoc)
102    * 
103    * @see
104    * com.googlecode.mgwt.dom.client.event.touch.TouchStartHandler#onTouchStart(com.googlecode.mgwt
105    * .dom.client.event.touch.TouchStartEvent)
106    */
107   @Override
108   public void onTouchStart(TouchStartEvent event) {
109     touchCount++;
110 
111     switch (state) {
112       case INVALID:
113         break;
114 
115       case READY:
116         state = State.FINDER_DOWN;
117 
118         x = event.getTouches().get(0).getPageX();
119         y = event.getTouches().get(0).getPageY();
120         break;
121 
122       case FINDER_DOWN:
123       default:
124         state = State.INVALID;
125         break;
126     }
127 
128   }
129 
130   /*
131    * (non-Javadoc)
132    * 
133    * @see
134    * com.googlecode.mgwt.dom.client.event.touch.TouchMoveHandler#onTouchMove(com.googlecode.mgwt
135    * .dom.client.event.touch.TouchMoveEvent)
136    */
137   @Override
138   public void onTouchMove(TouchMoveEvent event) {
139     Touch touch = event.getTouches().get(0);
140 
141     switch (state) {
142       case INVALID:
143 
144         break;
145       case READY:
146         // WTF?
147         state = State.INVALID;
148         break;
149       case FINDER_DOWN:
150 
151         // log(" X: " + touch.getPageX() + " old: " + touchStart.getPageX() + " test: " + x);
152 
153         if (Math.abs(touch.getPageX() - x) >= threshold) {
154           state = State.FOUND_DIRECTION;
155 
156           direction = touch.getPageX() - x > 0 ? DIRECTION.LEFT_TO_RIGHT : DIRECTION.RIGHT_TO_LEFT;
157 
158           SwipeStartEvent swipeStartEvent = new SwipeStartEvent(touch, touch.getPageX() - x, direction);
159 
160           getEventPropagator().fireEvent(source, swipeStartEvent);
161 
162         } else {
163           if (Math.abs(touch.getPageY() - y) >= threshold) {
164             state = State.FOUND_DIRECTION;
165 
166             direction = touch.getPageY() - y > 0 ? DIRECTION.TOP_TO_BOTTOM : DIRECTION.BOTTOM_TO_TOP;
167 
168             SwipeStartEvent swipeStartEvent = new SwipeStartEvent(touch, touch.getPageY() - y, direction);
169 
170             getEventPropagator().fireEvent(source, swipeStartEvent);
171 
172           }
173 
174         }
175         break;
176 
177       case FOUND_DIRECTION:
178 
179         switch (direction) {
180           case TOP_TO_BOTTOM:
181           case BOTTOM_TO_TOP:
182             lastDistance = Math.abs(touch.getPageY() - y);
183             getEventPropagator().fireEvent(source, new SwipeMoveEvent(touch, lastDistance > minDistance, lastDistance, direction));
184             break;
185 
186           case LEFT_TO_RIGHT:
187           case RIGHT_TO_LEFT:
188             lastDistance = Math.abs(touch.getPageX() - x);
189             getEventPropagator().fireEvent(source, new SwipeMoveEvent(touch, lastDistance > minDistance, lastDistance, direction));
190 
191             break;
192 
193           default:
194             break;
195         }
196 
197         break;
198 
199       default:
200         break;
201     }
202 
203   }
204 
205   public native void log(String l)/*-{
206 		$wnd.console.log(l);
207   }-*/;
208 
209   /*
210    * (non-Javadoc)
211    * 
212    * @see
213    * com.googlecode.mgwt.dom.client.event.touch.TouchEndHandler#onTouchEnd(com.googlecode.mgwt.dom
214    * .client.event.touch.TouchEndEvent)
215    */
216   @Override
217   public void onTouchEnd(TouchEndEvent event) {
218     touchCount--;
219 
220     switch (state) {
221       case FOUND_DIRECTION:
222         getEventPropagator().fireEvent(source, new SwipeEndEvent(lastDistance > minDistance, lastDistance, direction));
223         reset();
224         break;
225 
226       default:
227         reset();
228         break;
229     }
230 
231   }
232 
233   /*
234    * (non-Javadoc)
235    * 
236    * @see
237    * com.googlecode.mgwt.dom.client.event.touch.TouchCancelHandler#onTouchCanceled(com.googlecode
238    * .mgwt.dom.client.event.touch.TouchCancelEvent)
239    */
240   @Override
241   public void onTouchCanceled(TouchCancelEvent event) {
242     touchCount--;
243     if (touchCount <= 0)
244       reset();
245 
246   }
247 
248   /**
249    * the threshold before an event is fired (deadzone)
250    * 
251    * @return the threshold in px
252    */
253   public int getThreshold() {
254     return threshold;
255   }
256 
257   /**
258    * the distance that needs to be covered before counting as a swipe
259    * 
260    * @return the distance in px
261    */
262   public int getMinDistance() {
263     return minDistance;
264   }
265 
266   protected EventPropagator getEventPropagator() {
267     if (eventPropagator == null) {
268       if (DEFAULT_EVENT_PROPAGATOR == null) {
269         DEFAULT_EVENT_PROPAGATOR = GWT.create(EventPropagator.class);
270       }
271       eventPropagator = DEFAULT_EVENT_PROPAGATOR;
272     }
273     return eventPropagator;
274   }
275 
276   protected void setEventPropagator(EventPropagator eventPropagator) {
277     this.eventPropagator = eventPropagator;
278 
279   }
280 
281   private void reset() {
282     state = State.READY;
283     touchCount = 0;
284 
285   }
286 
287 }