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