]> git.tdb.fi Git - libs/gui.git/blob - source/input/gesturedetector.cpp
d626c5d08efc6dcbd909083651045c7f056c98db
[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_SWIPE_DOWN)
30                 return "Swipe down";
31         else if(btn==GESTURE_SWIPE_UP)
32                 return "Swipe up";
33         else if(btn==GESTURE_SWIPE_LEFT)
34                 return "Swipe left";
35         else if(btn==GESTURE_SWIPE_RIGHT)
36                 return "Swipe right";
37         else if(btn==GESTURE_PINCH)
38                 return "Pinch";
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
69 void GestureDetector::touch_up(unsigned btn)
70 {
71         if(btn>=MAX_POINTS)
72                 return;
73
74         TouchPoint &p = points[btn];
75         p.x += p.down_x;
76         p.y += p.down_y;
77         p.down = false;
78
79         if(active_points&(1<<btn))
80                 end_gesture();
81
82         if(current_gesture==GESTURE_NONE)
83         {
84                 // New gesture can't start until all points have been released.
85                 invalid_gesture = false;
86                 for(unsigned i=0; (i<MAX_POINTS && !invalid_gesture); ++i)
87                         invalid_gesture = points[i].down;
88         }
89 }
90
91 void GestureDetector::touch_move(unsigned axis, float value, float)
92 {
93         if(axis>=MAX_POINTS*2)
94                 return;
95
96         unsigned i = axis>>1;
97         TouchPoint &p = points[i];
98         // Track relative position when pressed, absolute when not.
99         if(axis&1)
100                 p.y = (p.down ? value-p.down_y : value);
101         else
102                 p.x = (p.down ? value-p.down_x : value);
103
104         if(p.down)
105         {
106                 if(p.x*p.x/threshold_x_sq+p.y*p.y/threshold_y_sq>=1)
107                         p.threshold_exceeded = true;
108
109                 if(current_gesture==GESTURE_NONE && !invalid_gesture)
110                         start_gesture();
111                 else if(active_points&(1<<i))
112                         update_progress();
113         }
114 }
115
116 void GestureDetector::start_gesture()
117 {
118         TouchPoint &p = points[0];
119         if(!p.down)
120                 return;
121
122         /* At least one point needs to have moved more than the threshold to start
123         the gesture. */
124         bool threshold_exceeded = false;
125         for(unsigned i=0; (i<MAX_POINTS && !threshold_exceeded); ++i)
126                 threshold_exceeded = (points[i].down && points[i].threshold_exceeded);
127         if(!threshold_exceeded)
128                 return;
129
130         invalid_gesture = false;
131         if(points[1].down)
132         {
133                 TouchPoint &p2 = points[1];
134                 float ddx = p.down_x-p2.down_x;
135                 float ddy = p.down_y-p2.down_y;
136                 float away = p.x*ddx+p.y*ddy;
137                 float turn = p.y*ddx-p.x*ddy;
138                 float away2 = -(p2.x*ddx+p2.y*ddy);
139                 float turn2 = -(p2.y*ddx-p2.x*ddy);
140                 if(away*away2>0 && abs(away)>abs(turn) && abs(away2)>abs(turn2))
141                         /* If the points moved away from or towards each other without rotating
142                         significantly, it's a pinch gesture. */
143                         current_gesture = GESTURE_PINCH;
144                 else
145                         invalid_gesture = true;
146
147                 if(current_gesture!=GESTURE_NONE)
148                 {
149                         active_points = 3;
150                         set_axis_value(0, (p.down_x+p2.down_x)/2, true);
151                         set_axis_value(1, (p.down_y+p2.down_y)/2, true);
152                 }
153         }
154         else
155         {
156                 // Allow a maximum deviation of about 26° to recognize a swipe gesture.
157                 if(abs(p.x)>2*abs(p.y))
158                         current_gesture = (p.x>0 ? GESTURE_SWIPE_RIGHT : GESTURE_SWIPE_LEFT);
159                 else if(abs(p.y)>2*abs(p.x))
160                         current_gesture = (p.y>0 ? GESTURE_SWIPE_UP : GESTURE_SWIPE_DOWN);
161                 else
162                         invalid_gesture = true;
163
164                 if(current_gesture!=GESTURE_NONE)
165                 {
166                         active_points = 1;
167                         set_axis_value(0, p.down_x, true);
168                         set_axis_value(1, p.down_y, true);
169                 }
170         }
171
172         update_progress();
173
174         if(current_gesture!=GESTURE_NONE)
175                 set_button_state(current_gesture, true, true);
176 }
177
178 void GestureDetector::update_progress()
179 {
180         TouchPoint &p = points[0];
181
182         if(current_gesture==GESTURE_SWIPE_DOWN)
183                 set_axis_value(2, -p.y, true);
184         else if(current_gesture==GESTURE_SWIPE_UP)
185                 set_axis_value(2, p.y, true);
186         else if(current_gesture==GESTURE_SWIPE_LEFT)
187                 set_axis_value(2, -p.x, true);
188         else if(current_gesture==GESTURE_SWIPE_RIGHT)
189                 set_axis_value(2, p.x, true);
190         else if(current_gesture==GESTURE_PINCH)
191         {
192                 TouchPoint &p2 = points[1];
193                 /* Pinch progress is the ratio between the current distance of the points
194                 and their distance when they were pressed. */
195                 float ddx = p.down_x-p2.down_x;
196                 float ddy = p.down_y-p2.down_y;
197                 float dx = ddx+p.x-p2.x;
198                 float dy = ddy+p.y-p2.y;
199                 set_axis_value(2, sqrt(dx*dx+dy*dy)/sqrt(ddx*ddx+ddy*ddy)-1, true);
200         }
201 }
202
203 void GestureDetector::end_gesture()
204 {
205         set_button_state(current_gesture, false, true);
206         set_axis_value(2, 0, false);
207         current_gesture = GESTURE_NONE;
208         active_points = 0;
209 }
210
211 void GestureDetector::window_resized(unsigned w, unsigned h)
212 {
213         threshold_x_sq = 2500.0/(w*w);
214         threshold_y_sq = 2500.0/(h*h);
215 }
216
217
218 GestureDetector::TouchPoint::TouchPoint():
219         down(false),
220         down_x(0),
221         down_y(0),
222         x(0),
223         y(0)
224 { }
225
226 } // namespace Input
227 } // namespace Msp