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