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