1 #define _USE_MATH_DEFINES
3 #include <sigc++/bind_return.h>
4 #include "gesturedetector.h"
5 #include "touchscreen.h"
12 GestureDetector::GestureDetector(Touchscreen &ts):
15 current_gesture(GESTURE_NONE),
16 pending_tap(GESTURE_NONE),
17 invalid_gesture(false)
21 touchscreen.signal_button_press.connect(sigc::bind_return(sigc::mem_fun(this, &GestureDetector::touch_down), false));
22 touchscreen.signal_button_release.connect(sigc::bind_return(sigc::mem_fun(this, &GestureDetector::touch_up), false));
23 touchscreen.signal_axis_motion.connect(sigc::bind_return(sigc::mem_fun(this, &GestureDetector::touch_move), false));
24 Graphics::Window &window = touchscreen.get_window();
25 window.signal_resize.connect(sigc::mem_fun(this, &GestureDetector::window_resized));
26 window_resized(window.get_width(), window.get_height());
29 string GestureDetector::get_button_name(unsigned btn) const
33 else if(btn==GESTURE_TAP_2)
34 return "Two-finger tap";
35 else if(btn==GESTURE_TAP_3)
36 return "Three-finger tap";
37 else if(btn==GESTURE_DRAG)
39 else if(btn==GESTURE_DRAG_2)
40 return "Two-finger drag";
41 else if(btn==GESTURE_DRAG_3)
42 return "Three-finger drag";
43 else if(btn==GESTURE_PINCH)
45 else if(btn==GESTURE_ROTATE)
48 return Device::get_button_name(btn);
51 string GestureDetector::get_axis_name(unsigned axis) const
62 return Device::get_axis_name(axis);
65 void GestureDetector::touch_down(unsigned btn)
70 TouchPoint &p = points[btn];
76 p.threshold_exceeded = false;
78 if(current_gesture==GESTURE_NONE && !invalid_gesture)
81 pending_tap = GESTURE_TAP;
83 pending_tap = GESTURE_TAP_2;
85 pending_tap = GESTURE_TAP_3;
89 void GestureDetector::touch_up(unsigned btn)
94 TouchPoint &p = points[btn];
99 if(btn<gesture_points(current_gesture))
102 if(current_gesture==GESTURE_NONE)
104 // New gesture can't start until all points have been released.
105 invalid_gesture = false;
106 for(unsigned i=0; (i<MAX_POINTS && !invalid_gesture); ++i)
107 invalid_gesture = points[i].down;
109 if(!invalid_gesture && pending_tap!=GESTURE_NONE)
111 set_gesture_location(gesture_points(pending_tap));
112 set_button_state(pending_tap, true, true);
113 set_button_state(pending_tap, false, true);
115 pending_tap = GESTURE_NONE;
120 void GestureDetector::touch_move(unsigned axis, float value, float)
122 if(axis>=MAX_POINTS*2)
125 unsigned i = axis>>1;
126 TouchPoint &p = points[i];
127 // Track relative position when pressed, absolute when not.
129 p.y = (p.down ? value-p.down_y : value);
131 p.x = (p.down ? value-p.down_x : value);
135 if(p.x*p.x/threshold_x_sq+p.y*p.y/threshold_y_sq>=1)
137 p.threshold_exceeded = true;
138 pending_tap = GESTURE_NONE;
141 if(current_gesture==GESTURE_NONE && !invalid_gesture)
143 else if(i<gesture_points(current_gesture))
148 void GestureDetector::start_gesture()
150 TouchPoint &p = points[0];
154 /* All held points need to have moved more than the threshold to start the
156 bool threshold_exceeded = true;
157 for(unsigned i=0; (i<MAX_POINTS && threshold_exceeded); ++i)
158 if(points[i].down && !points[i].threshold_exceeded)
159 threshold_exceeded = false;
160 if(!threshold_exceeded)
163 invalid_gesture = false;
166 bool same_direction = true;
167 for(unsigned i=0; (same_direction && i<2); ++i)
168 for(unsigned j=i+1; (same_direction && j<3); ++j)
170 TouchPoint &pi = points[i];
171 TouchPoint &pj = points[j];
172 same_direction = ((pi.x*pj.x+pi.y*pj.y)>2*abs(pi.x*pj.y-pi.y*pj.x));
176 current_gesture = GESTURE_DRAG_3;
178 else if(points[1].down)
180 TouchPoint &p2 = points[1];
181 float ddx = p.down_x-p2.down_x;
182 float ddy = p.down_y-p2.down_y;
183 float away = p.x*ddx+p.y*ddy;
184 float turn = p.y*ddx-p.x*ddy;
185 float away2 = -(p2.x*ddx+p2.y*ddy);
186 float turn2 = -(p2.y*ddx-p2.x*ddy);
187 if(away*away2>0 && abs(away)>abs(turn) && abs(away2)>abs(turn2))
188 /* If the points moved away from or towards each other without rotating
189 significantly, it's a pinch gesture. */
190 current_gesture = GESTURE_PINCH;
191 else if(turn*turn2>0 && abs(turn)>abs(away) && abs(turn2)>abs(away2))
192 /* If the points both turned in the same direction without significant
193 changes in distance, it's a rotate gesture. */
194 current_gesture = GESTURE_ROTATE;
195 else if((p.x*p2.x+p.y*p2.y)>2*abs(p.x*p2.y-p.y*p2.x))
196 // If both points moved in the same direction, it's a two-finger drag.
197 current_gesture = GESTURE_DRAG_2;
200 current_gesture = GESTURE_DRAG;
202 if(current_gesture!=GESTURE_NONE)
204 set_gesture_location(gesture_points(current_gesture));
206 set_button_state(current_gesture, true, true);
209 invalid_gesture = true;
212 void GestureDetector::set_gesture_location(unsigned n_points)
216 for(unsigned i=0; i<n_points; ++i)
218 x += points[i].down_x;
219 y += points[i].down_y;
222 set_axis_value(0, x/n_points, true);
223 set_axis_value(1, y/n_points, true);
226 void GestureDetector::set_gesture_delta(unsigned n_points)
230 for(unsigned i=0; i<n_points; ++i)
236 set_axis_value(2, x/n_points, true);
237 set_axis_value(3, y/n_points, true);
240 void GestureDetector::update_progress()
242 if(current_gesture>=GESTURE_DRAG && current_gesture<=GESTURE_DRAG_3)
243 set_gesture_delta(gesture_points(current_gesture));
244 else if(current_gesture==GESTURE_PINCH || current_gesture==GESTURE_ROTATE)
246 TouchPoint &p = points[0];
247 TouchPoint &p2 = points[1];
248 /* Pinch progress is the ratio between the current distance of the points
249 and their distance when they were pressed. */
250 float ddx = p.down_x-p2.down_x;
251 float ddy = p.down_y-p2.down_y;
252 float dx = ddx+p.x-p2.x;
253 float dy = ddy+p.y-p2.y;
254 if(current_gesture==GESTURE_PINCH)
256 set_axis_value(2, sqrt(dx*dx+dy*dy)/sqrt(ddx*ddx+ddy*ddy)-1, true);
257 set_axis_value(3, 0, true);
259 else if(current_gesture==GESTURE_ROTATE)
261 set_axis_value(2, atan2(dy*ddx-dx*ddy, dx*ddx+dy*ddy)/M_PI/2, true);
262 set_axis_value(3, 0, true);
267 void GestureDetector::end_gesture()
269 set_button_state(current_gesture, false, true);
270 set_axis_value(2, 0, false);
271 current_gesture = GESTURE_NONE;
272 pending_tap = GESTURE_NONE;
275 void GestureDetector::window_resized(unsigned w, unsigned h)
277 threshold_x_sq = 2500.0/(w*w);
278 threshold_y_sq = 2500.0/(h*h);
282 GestureDetector::TouchPoint::TouchPoint():
288 threshold_exceeded(false)
292 unsigned gesture_points(Gesture gesture)
296 case GESTURE_NONE: return 0;
297 case GESTURE_TAP: return 1;
298 case GESTURE_TAP_2: return 2;
299 case GESTURE_TAP_3: return 3;
300 case GESTURE_DRAG: return 1;
301 case GESTURE_DRAG_2: return 2;
302 case GESTURE_DRAG_3: return 3;
303 case GESTURE_PINCH: return 2;
304 case GESTURE_ROTATE: return 2;
305 default: throw invalid_argument("gesture_points");