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