]> git.tdb.fi Git - libs/gui.git/blob - source/input/gesturedetector.cpp
62ac3f050d09c9c849b7e0c6c62ecf8e3185a3bf
[libs/gui.git] / source / input / gesturedetector.cpp
1 #define _USE_MATH_DEFINES
2 #include <cmath>
3 #include <sigc++/bind_return.h>
4 #include "gesturedetector.h"
5 #include "touchscreen.h"
6
7 using namespace std;
8
9 namespace Msp {
10 namespace Input {
11
12 GestureDetector::GestureDetector(Touchscreen &ts):
13         touchscreen(ts),
14         current_gesture(GESTURE_NONE),
15         pending_tap(GESTURE_NONE),
16         invalid_gesture(false)
17 {
18         name = "Gesture";
19
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());
26 }
27
28 string GestureDetector::get_button_name(unsigned btn) const
29 {
30         if(btn==GESTURE_TAP)
31                 return "Tap";
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)
37                 return "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)
43                 return "Pinch";
44         else if(btn==GESTURE_ROTATE)
45                 return "Rotate";
46         else
47                 return Device::get_button_name(btn);
48 }
49
50 string GestureDetector::get_axis_name(unsigned axis) const
51 {
52         if(axis==0)
53                 return "X";
54         else if(axis==1)
55                 return "Y";
56         else if(axis==2)
57                 return "Delta X";
58         else if(axis==3)
59                 return "Delta Y";
60         else
61                 return Device::get_axis_name(axis);
62 }
63
64 void GestureDetector::touch_down(unsigned btn)
65 {
66         if(btn>=MAX_POINTS)
67                 return;
68
69         TouchPoint &p = points[btn];
70         p.down = true;
71         p.down_x = p.x;
72         p.down_y = p.y;
73         p.x = 0;
74         p.y = 0;
75         p.threshold_exceeded = false;
76
77         if(current_gesture==GESTURE_NONE && !invalid_gesture)
78         {
79                 if(btn==0)
80                         pending_tap = GESTURE_TAP;
81                 else if(btn==1)
82                         pending_tap = GESTURE_TAP_2;
83                 else if(btn==2)
84                         pending_tap = GESTURE_TAP_3;
85         }
86 }
87
88 void GestureDetector::touch_up(unsigned btn)
89 {
90         if(btn>=MAX_POINTS)
91                 return;
92
93         TouchPoint &p = points[btn];
94         p.x += p.down_x;
95         p.y += p.down_y;
96         p.down = false;
97
98         if(btn<gesture_points(current_gesture))
99                 end_gesture();
100
101         if(current_gesture==GESTURE_NONE)
102         {
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;
107
108                 if(!invalid_gesture && pending_tap!=GESTURE_NONE)
109                 {
110                         set_gesture_location(gesture_points(pending_tap));
111                         set_button_state(pending_tap, true, true);
112                         set_button_state(pending_tap, false, true);
113
114                         pending_tap = GESTURE_NONE;
115                 }
116         }
117 }
118
119 void GestureDetector::touch_move(unsigned axis, float value, float)
120 {
121         if(axis>=MAX_POINTS*2)
122                 return;
123
124         unsigned i = axis>>1;
125         TouchPoint &p = points[i];
126         // Track relative position when pressed, absolute when not.
127         if(axis&1)
128                 p.y = (p.down ? value-p.down_y : value);
129         else
130                 p.x = (p.down ? value-p.down_x : value);
131
132         if(p.down)
133         {
134                 if(p.x*p.x/threshold_x_sq+p.y*p.y/threshold_y_sq>=1)
135                 {
136                         p.threshold_exceeded = true;
137                         pending_tap = GESTURE_NONE;
138                 }
139
140                 if(current_gesture==GESTURE_NONE && !invalid_gesture)
141                         start_gesture();
142                 else if(i<gesture_points(current_gesture))
143                         update_progress();
144         }
145 }
146
147 void GestureDetector::start_gesture()
148 {
149         TouchPoint &p = points[0];
150         if(!p.down)
151                 return;
152
153         /* At least one point needs to have moved more than the threshold to start
154         the gesture. */
155         bool threshold_exceeded = false;
156         for(unsigned i=0; (i<MAX_POINTS && !threshold_exceeded); ++i)
157                 threshold_exceeded = (points[i].down && points[i].threshold_exceeded);
158         if(!threshold_exceeded)
159                 return;
160
161         invalid_gesture = false;
162         if(points[2].down)
163         {
164                 bool same_direction = true;
165                 for(unsigned i=0; (same_direction && i<2); ++i)
166                         for(unsigned j=i+1; (same_direction && j<3); ++j)
167                         {
168                                 TouchPoint &pi = points[i];
169                                 TouchPoint &pj = points[j];
170                                 same_direction = ((pi.x*pj.x+pi.y*pj.y)>2*abs(pi.x*pj.y-pi.y*pj.x));
171                         }
172
173                 if(same_direction)
174                         current_gesture = GESTURE_DRAG_3;
175         }
176         else if(points[1].down)
177         {
178                 TouchPoint &p2 = points[1];
179                 float ddx = p.down_x-p2.down_x;
180                 float ddy = p.down_y-p2.down_y;
181                 float away = p.x*ddx+p.y*ddy;
182                 float turn = p.y*ddx-p.x*ddy;
183                 float away2 = -(p2.x*ddx+p2.y*ddy);
184                 float turn2 = -(p2.y*ddx-p2.x*ddy);
185                 if(away*away2>0 && abs(away)>abs(turn) && abs(away2)>abs(turn2))
186                         /* If the points moved away from or towards each other without rotating
187                         significantly, it's a pinch gesture. */
188                         current_gesture = GESTURE_PINCH;
189                 else if(turn*turn2>0 && abs(turn)>abs(away) && abs(turn2)>abs(away2))
190                         /* If the points both turned in the same direction without significant
191                         changes in distance, it's a rotate gesture. */
192                         current_gesture = GESTURE_ROTATE;
193                 else if((p.x*p2.x+p.y*p2.y)>2*abs(p.x*p2.y-p.y*p2.x))
194                         // If both points moved in the same direction, it's a two-finger drag.
195                         current_gesture = GESTURE_DRAG_2;
196         }
197         else
198                 current_gesture = GESTURE_DRAG;
199
200         if(current_gesture!=GESTURE_NONE)
201         {
202                 set_gesture_location(gesture_points(current_gesture));
203                 update_progress();
204                 set_button_state(current_gesture, true, true);
205         }
206         else
207                 invalid_gesture = true;
208 }
209
210 void GestureDetector::set_gesture_location(unsigned n_points)
211 {
212         float x = 0;
213         float y = 0;
214         for(unsigned i=0; i<n_points; ++i)
215         {
216                 x += points[i].down_x;
217                 y += points[i].down_y;
218         }
219
220         set_axis_value(0, x/n_points, true);
221         set_axis_value(1, y/n_points, true);
222 }
223
224 void GestureDetector::set_gesture_delta(unsigned n_points)
225 {
226         float x = 0;
227         float y = 0;
228         for(unsigned i=0; i<n_points; ++i)
229         {
230                 x += points[i].x;
231                 y += points[i].y;
232         }
233
234         set_axis_value(2, x/n_points, true);
235         set_axis_value(3, y/n_points, true);
236 }
237
238 void GestureDetector::update_progress()
239 {
240         if(current_gesture>=GESTURE_DRAG && current_gesture<=GESTURE_DRAG_3)
241                 set_gesture_delta(gesture_points(current_gesture));
242         else if(current_gesture==GESTURE_PINCH || current_gesture==GESTURE_ROTATE)
243         {
244                 TouchPoint &p = points[0];
245                 TouchPoint &p2 = points[1];
246                 /* Pinch progress is the ratio between the current distance of the points
247                 and their distance when they were pressed. */
248                 float ddx = p.down_x-p2.down_x;
249                 float ddy = p.down_y-p2.down_y;
250                 float dx = ddx+p.x-p2.x;
251                 float dy = ddy+p.y-p2.y;
252                 if(current_gesture==GESTURE_PINCH)
253                 {
254                         set_axis_value(2, sqrt(dx*dx+dy*dy)/sqrt(ddx*ddx+ddy*ddy)-1, true);
255                         set_axis_value(3, 0, true);
256                 }
257                 else if(current_gesture==GESTURE_ROTATE)
258                 {
259                         set_axis_value(2, atan2(dy*ddx-dx*ddy, dx*ddx+dy*ddy)/M_PI/2, true);
260                         set_axis_value(3, 0, true);
261                 }
262         }
263 }
264
265 void GestureDetector::end_gesture()
266 {
267         set_button_state(current_gesture, false, true);
268         set_axis_value(2, 0, false);
269         current_gesture = GESTURE_NONE;
270         pending_tap = GESTURE_NONE;
271 }
272
273 void GestureDetector::window_resized(unsigned w, unsigned h)
274 {
275         threshold_x_sq = 2500.0/(w*w);
276         threshold_y_sq = 2500.0/(h*h);
277 }
278
279
280 GestureDetector::TouchPoint::TouchPoint():
281         down(false),
282         down_x(0),
283         down_y(0),
284         x(0),
285         y(0),
286         threshold_exceeded(false)
287 { }
288
289
290 unsigned gesture_points(Gesture gesture)
291 {
292         switch(gesture)
293         {
294         case GESTURE_NONE: return 0;
295         case GESTURE_TAP: return 1;
296         case GESTURE_TAP_2: return 2;
297         case GESTURE_TAP_3: return 3;
298         case GESTURE_DRAG: return 1;
299         case GESTURE_DRAG_2: return 2;
300         case GESTURE_DRAG_3: return 3;
301         case GESTURE_PINCH: return 2;
302         case GESTURE_ROTATE: return 2;
303         default: throw invalid_argument("gesture_points");
304         }
305 }
306
307 } // namespace Input
308 } // namespace Msp