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