]> git.tdb.fi Git - libs/gui.git/blob - source/input/gesturedetector.cpp
Include the matching header first in .cpp files
[libs/gui.git] / source / input / gesturedetector.cpp
1 #include "gesturedetector.h"
2 #define _USE_MATH_DEFINES
3 #include <cmath>
4 #include <sigc++/bind_return.h>
5 #include "touchscreen.h"
6
7 using namespace std;
8
9 namespace Msp {
10 namespace Input {
11
12 GestureDetector::GestureDetector(Touchscreen &ts):
13         Device(UNSPECIFIED),
14         touchscreen(ts),
15         current_gesture(GESTURE_NONE),
16         pending_tap(GESTURE_NONE),
17         invalid_gesture(false)
18 {
19         name = "Gesture";
20
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());
27 }
28
29 string GestureDetector::get_button_name(unsigned btn) const
30 {
31         if(btn==GESTURE_TAP)
32                 return "Tap";
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)
38                 return "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)
44                 return "Pinch";
45         else if(btn==GESTURE_ROTATE)
46                 return "Rotate";
47         else
48                 return Device::get_button_name(btn);
49 }
50
51 string GestureDetector::get_axis_name(unsigned axis) const
52 {
53         if(axis==0)
54                 return "X";
55         else if(axis==1)
56                 return "Y";
57         else if(axis==2)
58                 return "Delta X";
59         else if(axis==3)
60                 return "Delta Y";
61         else
62                 return Device::get_axis_name(axis);
63 }
64
65 void GestureDetector::touch_down(unsigned btn)
66 {
67         if(btn>=MAX_POINTS)
68                 return;
69
70         TouchPoint &p = points[btn];
71         p.down = true;
72         p.down_x = p.x;
73         p.down_y = p.y;
74         p.x = 0;
75         p.y = 0;
76         p.threshold_exceeded = false;
77
78         if(current_gesture==GESTURE_NONE && !invalid_gesture)
79         {
80                 if(btn==0)
81                         pending_tap = GESTURE_TAP;
82                 else if(btn==1)
83                         pending_tap = GESTURE_TAP_2;
84                 else if(btn==2)
85                         pending_tap = GESTURE_TAP_3;
86         }
87 }
88
89 void GestureDetector::touch_up(unsigned btn)
90 {
91         if(btn>=MAX_POINTS)
92                 return;
93
94         TouchPoint &p = points[btn];
95         p.x += p.down_x;
96         p.y += p.down_y;
97         p.down = false;
98
99         if(btn<gesture_points(current_gesture))
100                 end_gesture();
101
102         if(current_gesture==GESTURE_NONE)
103         {
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;
108
109                 if(!invalid_gesture && pending_tap!=GESTURE_NONE)
110                 {
111                         set_gesture_location(gesture_points(pending_tap));
112                         set_button_state(pending_tap, true, true);
113                         set_button_state(pending_tap, false, true);
114
115                         pending_tap = GESTURE_NONE;
116                 }
117         }
118 }
119
120 void GestureDetector::touch_move(unsigned axis, float value, float)
121 {
122         if(axis>=MAX_POINTS*2)
123                 return;
124
125         unsigned i = axis>>1;
126         TouchPoint &p = points[i];
127         // Track relative position when pressed, absolute when not.
128         if(axis&1)
129                 p.y = (p.down ? value-p.down_y : value);
130         else
131                 p.x = (p.down ? value-p.down_x : value);
132
133         if(p.down)
134         {
135                 if(p.x*p.x/threshold_x_sq+p.y*p.y/threshold_y_sq>=1)
136                 {
137                         p.threshold_exceeded = true;
138                         pending_tap = GESTURE_NONE;
139                 }
140
141                 if(current_gesture==GESTURE_NONE && !invalid_gesture)
142                         start_gesture();
143                 else if(i<gesture_points(current_gesture))
144                         update_progress();
145         }
146 }
147
148 void GestureDetector::start_gesture()
149 {
150         TouchPoint &p = points[0];
151         if(!p.down)
152                 return;
153
154         /* All held points need to have moved more than the threshold to start the
155         gesture. */
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)
161                 return;
162
163         invalid_gesture = false;
164         if(points[2].down)
165         {
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)
169                         {
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));
173                         }
174
175                 if(same_direction)
176                         current_gesture = GESTURE_DRAG_3;
177         }
178         else if(points[1].down)
179         {
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;
198         }
199         else
200                 current_gesture = GESTURE_DRAG;
201
202         if(current_gesture!=GESTURE_NONE)
203         {
204                 set_gesture_location(gesture_points(current_gesture));
205                 update_progress();
206                 set_button_state(current_gesture, true, true);
207         }
208         else
209                 invalid_gesture = true;
210 }
211
212 void GestureDetector::set_gesture_location(unsigned n_points)
213 {
214         float x = 0;
215         float y = 0;
216         for(unsigned i=0; i<n_points; ++i)
217         {
218                 x += points[i].down_x;
219                 y += points[i].down_y;
220         }
221
222         set_axis_value(0, x/n_points, true);
223         set_axis_value(1, y/n_points, true);
224 }
225
226 void GestureDetector::set_gesture_delta(unsigned n_points)
227 {
228         float x = 0;
229         float y = 0;
230         for(unsigned i=0; i<n_points; ++i)
231         {
232                 x += points[i].x;
233                 y += points[i].y;
234         }
235
236         set_axis_value(2, x/n_points, true);
237         set_axis_value(3, y/n_points, true);
238 }
239
240 void GestureDetector::update_progress()
241 {
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)
245         {
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)
255                 {
256                         set_axis_value(2, sqrt(dx*dx+dy*dy)/sqrt(ddx*ddx+ddy*ddy)-1, true);
257                         set_axis_value(3, 0, true);
258                 }
259                 else if(current_gesture==GESTURE_ROTATE)
260                 {
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);
263                 }
264         }
265 }
266
267 void GestureDetector::end_gesture()
268 {
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;
273 }
274
275 void GestureDetector::window_resized(unsigned w, unsigned h)
276 {
277         threshold_x_sq = 2500.0/(w*w);
278         threshold_y_sq = 2500.0/(h*h);
279 }
280
281
282 GestureDetector::TouchPoint::TouchPoint():
283         down(false),
284         down_x(0),
285         down_y(0),
286         x(0),
287         y(0),
288         threshold_exceeded(false)
289 { }
290
291
292 unsigned gesture_points(Gesture gesture)
293 {
294         switch(gesture)
295         {
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");
306         }
307 }
308
309 } // namespace Input
310 } // namespace Msp