1 #define _USE_MATH_DEFINES
3 #include <sigc++/bind_return.h>
4 #include "gesturedetector.h"
5 #include "touchscreen.h"
12 GestureDetector::GestureDetector(Touchscreen &ts):
14 current_gesture(GESTURE_NONE),
15 pending_tap(GESTURE_NONE),
16 invalid_gesture(false)
20 touchscreen.signal_button_press.connect(sigc::bind_return(sigc::mem_fun(this, &GestureDetector::touch_down), false));
21 touchscreen.signal_button_release.connect(sigc::bind_return(sigc::mem_fun(this, &GestureDetector::touch_up), false));
22 touchscreen.signal_axis_motion.connect(sigc::bind_return(sigc::mem_fun(this, &GestureDetector::touch_move), false));
23 Graphics::Window &window = touchscreen.get_window();
24 window.signal_resize.connect(sigc::mem_fun(this, &GestureDetector::window_resized));
25 window_resized(window.get_width(), window.get_height());
28 string GestureDetector::get_button_name(unsigned btn) const
32 else if(btn==GESTURE_TAP_2)
33 return "Two-finger tap";
34 else if(btn==GESTURE_TAP_3)
35 return "Three-finger tap";
36 else if(btn==GESTURE_DRAG)
38 else if(btn==GESTURE_DRAG_2)
39 return "Two-finger drag";
40 else if(btn==GESTURE_DRAG_3)
41 return "Three-finger drag";
42 else if(btn==GESTURE_PINCH)
44 else if(btn==GESTURE_ROTATE)
47 return Device::get_button_name(btn);
50 string GestureDetector::get_axis_name(unsigned axis) const
61 return Device::get_axis_name(axis);
64 void GestureDetector::touch_down(unsigned btn)
69 TouchPoint &p = points[btn];
75 p.threshold_exceeded = false;
77 if(current_gesture==GESTURE_NONE && !invalid_gesture)
80 pending_tap = GESTURE_TAP;
82 pending_tap = GESTURE_TAP_2;
84 pending_tap = GESTURE_TAP_3;
88 void GestureDetector::touch_up(unsigned btn)
93 TouchPoint &p = points[btn];
98 if(btn<gesture_points(current_gesture))
101 if(current_gesture==GESTURE_NONE)
103 // New gesture can't start until all points have been released.
104 invalid_gesture = false;
105 for(unsigned i=0; (i<MAX_POINTS && !invalid_gesture); ++i)
106 invalid_gesture = points[i].down;
108 if(!invalid_gesture && pending_tap!=GESTURE_NONE)
110 set_gesture_location(gesture_points(pending_tap));
111 set_button_state(pending_tap, true, true);
112 set_button_state(pending_tap, false, true);
114 pending_tap = GESTURE_NONE;
119 void GestureDetector::touch_move(unsigned axis, float value, float)
121 if(axis>=MAX_POINTS*2)
124 unsigned i = axis>>1;
125 TouchPoint &p = points[i];
126 // Track relative position when pressed, absolute when not.
128 p.y = (p.down ? value-p.down_y : value);
130 p.x = (p.down ? value-p.down_x : value);
134 if(p.x*p.x/threshold_x_sq+p.y*p.y/threshold_y_sq>=1)
136 p.threshold_exceeded = true;
137 pending_tap = GESTURE_NONE;
140 if(current_gesture==GESTURE_NONE && !invalid_gesture)
142 else if(i<gesture_points(current_gesture))
147 void GestureDetector::start_gesture()
149 TouchPoint &p = points[0];
153 /* All held points need to have moved more than the threshold to start the
155 bool threshold_exceeded = true;
156 for(unsigned i=0; (i<MAX_POINTS && threshold_exceeded); ++i)
157 if(points[i].down && !points[i].threshold_exceeded)
158 threshold_exceeded = false;
159 if(!threshold_exceeded)
162 invalid_gesture = false;
165 bool same_direction = true;
166 for(unsigned i=0; (same_direction && i<2); ++i)
167 for(unsigned j=i+1; (same_direction && j<3); ++j)
169 TouchPoint &pi = points[i];
170 TouchPoint &pj = points[j];
171 same_direction = ((pi.x*pj.x+pi.y*pj.y)>2*abs(pi.x*pj.y-pi.y*pj.x));
175 current_gesture = GESTURE_DRAG_3;
177 else if(points[1].down)
179 TouchPoint &p2 = points[1];
180 float ddx = p.down_x-p2.down_x;
181 float ddy = p.down_y-p2.down_y;
182 float away = p.x*ddx+p.y*ddy;
183 float turn = p.y*ddx-p.x*ddy;
184 float away2 = -(p2.x*ddx+p2.y*ddy);
185 float turn2 = -(p2.y*ddx-p2.x*ddy);
186 if(away*away2>0 && abs(away)>abs(turn) && abs(away2)>abs(turn2))
187 /* If the points moved away from or towards each other without rotating
188 significantly, it's a pinch gesture. */
189 current_gesture = GESTURE_PINCH;
190 else if(turn*turn2>0 && abs(turn)>abs(away) && abs(turn2)>abs(away2))
191 /* If the points both turned in the same direction without significant
192 changes in distance, it's a rotate gesture. */
193 current_gesture = GESTURE_ROTATE;
194 else if((p.x*p2.x+p.y*p2.y)>2*abs(p.x*p2.y-p.y*p2.x))
195 // If both points moved in the same direction, it's a two-finger drag.
196 current_gesture = GESTURE_DRAG_2;
199 current_gesture = GESTURE_DRAG;
201 if(current_gesture!=GESTURE_NONE)
203 set_gesture_location(gesture_points(current_gesture));
205 set_button_state(current_gesture, true, true);
208 invalid_gesture = true;
211 void GestureDetector::set_gesture_location(unsigned n_points)
215 for(unsigned i=0; i<n_points; ++i)
217 x += points[i].down_x;
218 y += points[i].down_y;
221 set_axis_value(0, x/n_points, true);
222 set_axis_value(1, y/n_points, true);
225 void GestureDetector::set_gesture_delta(unsigned n_points)
229 for(unsigned i=0; i<n_points; ++i)
235 set_axis_value(2, x/n_points, true);
236 set_axis_value(3, y/n_points, true);
239 void GestureDetector::update_progress()
241 if(current_gesture>=GESTURE_DRAG && current_gesture<=GESTURE_DRAG_3)
242 set_gesture_delta(gesture_points(current_gesture));
243 else if(current_gesture==GESTURE_PINCH || current_gesture==GESTURE_ROTATE)
245 TouchPoint &p = points[0];
246 TouchPoint &p2 = points[1];
247 /* Pinch progress is the ratio between the current distance of the points
248 and their distance when they were pressed. */
249 float ddx = p.down_x-p2.down_x;
250 float ddy = p.down_y-p2.down_y;
251 float dx = ddx+p.x-p2.x;
252 float dy = ddy+p.y-p2.y;
253 if(current_gesture==GESTURE_PINCH)
255 set_axis_value(2, sqrt(dx*dx+dy*dy)/sqrt(ddx*ddx+ddy*ddy)-1, true);
256 set_axis_value(3, 0, true);
258 else if(current_gesture==GESTURE_ROTATE)
260 set_axis_value(2, atan2(dy*ddx-dx*ddy, dx*ddx+dy*ddy)/M_PI/2, true);
261 set_axis_value(3, 0, true);
266 void GestureDetector::end_gesture()
268 set_button_state(current_gesture, false, true);
269 set_axis_value(2, 0, false);
270 current_gesture = GESTURE_NONE;
271 pending_tap = GESTURE_NONE;
274 void GestureDetector::window_resized(unsigned w, unsigned h)
276 threshold_x_sq = 2500.0/(w*w);
277 threshold_y_sq = 2500.0/(h*h);
281 GestureDetector::TouchPoint::TouchPoint():
287 threshold_exceeded(false)
291 unsigned gesture_points(Gesture gesture)
295 case GESTURE_NONE: return 0;
296 case GESTURE_TAP: return 1;
297 case GESTURE_TAP_2: return 2;
298 case GESTURE_TAP_3: return 3;
299 case GESTURE_DRAG: return 1;
300 case GESTURE_DRAG_2: return 2;
301 case GESTURE_DRAG_3: return 3;
302 case GESTURE_PINCH: return 2;
303 case GESTURE_ROTATE: return 2;
304 default: throw invalid_argument("gesture_points");