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