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