+#include "gesturedetector.h"
+#define _USE_MATH_DEFINES
#include <cmath>
#include <sigc++/bind_return.h>
-#include "gesturedetector.h"
#include "touchscreen.h"
using namespace std;
namespace Input {
GestureDetector::GestureDetector(Touchscreen &ts):
- touchscreen(ts),
- current_gesture(GESTURE_NONE),
- active_points(0),
- invalid_gesture(false)
+ Device(UNSPECIFIED),
+ touchscreen(ts)
{
name = "Gesture";
string GestureDetector::get_button_name(unsigned btn) const
{
- if(btn==GESTURE_SWIPE_DOWN)
- return "Swipe down";
- else if(btn==GESTURE_SWIPE_UP)
- return "Swipe up";
- else if(btn==GESTURE_SWIPE_LEFT)
- return "Swipe left";
- else if(btn==GESTURE_SWIPE_RIGHT)
- return "Swipe right";
+ if(btn==GESTURE_TAP)
+ return "Tap";
+ else if(btn==GESTURE_TAP_2)
+ return "Two-finger tap";
+ else if(btn==GESTURE_TAP_3)
+ return "Three-finger tap";
+ else if(btn==GESTURE_DRAG)
+ return "Drag";
+ else if(btn==GESTURE_DRAG_2)
+ return "Two-finger drag";
+ else if(btn==GESTURE_DRAG_3)
+ return "Three-finger drag";
else if(btn==GESTURE_PINCH)
return "Pinch";
+ else if(btn==GESTURE_ROTATE)
+ return "Rotate";
else
return Device::get_button_name(btn);
}
else if(axis==1)
return "Y";
else if(axis==2)
- return "Progress";
+ return "Delta X";
+ else if(axis==3)
+ return "Delta Y";
else
return Device::get_axis_name(axis);
}
p.x = 0;
p.y = 0;
p.threshold_exceeded = false;
+
+ if(current_gesture==GESTURE_NONE && !invalid_gesture)
+ {
+ if(btn==0)
+ pending_tap = GESTURE_TAP;
+ else if(btn==1)
+ pending_tap = GESTURE_TAP_2;
+ else if(btn==2)
+ pending_tap = GESTURE_TAP_3;
+ }
}
void GestureDetector::touch_up(unsigned btn)
p.y += p.down_y;
p.down = false;
- if(active_points&(1<<btn))
+ if(btn<gesture_points(current_gesture))
end_gesture();
if(current_gesture==GESTURE_NONE)
invalid_gesture = false;
for(unsigned i=0; (i<MAX_POINTS && !invalid_gesture); ++i)
invalid_gesture = points[i].down;
+
+ if(!invalid_gesture && pending_tap!=GESTURE_NONE)
+ {
+ set_gesture_location(gesture_points(pending_tap));
+ set_button_state(pending_tap, true, true);
+ set_button_state(pending_tap, false, true);
+
+ pending_tap = GESTURE_NONE;
+ }
}
}
if(p.down)
{
if(p.x*p.x/threshold_x_sq+p.y*p.y/threshold_y_sq>=1)
+ {
p.threshold_exceeded = true;
+ pending_tap = GESTURE_NONE;
+ }
if(current_gesture==GESTURE_NONE && !invalid_gesture)
start_gesture();
- else if(active_points&(1<<i))
+ else if(i<gesture_points(current_gesture))
update_progress();
}
}
if(!p.down)
return;
- /* At least one point needs to have moved more than the threshold to start
- the gesture. */
- bool threshold_exceeded = false;
- for(unsigned i=0; (i<MAX_POINTS && !threshold_exceeded); ++i)
- threshold_exceeded = (points[i].down && points[i].threshold_exceeded);
+ /* All held points need to have moved more than the threshold to start the
+ gesture. */
+ bool threshold_exceeded = true;
+ for(unsigned i=0; (i<MAX_POINTS && threshold_exceeded); ++i)
+ if(points[i].down && !points[i].threshold_exceeded)
+ threshold_exceeded = false;
if(!threshold_exceeded)
return;
invalid_gesture = false;
- if(points[1].down)
+ if(points[2].down)
+ {
+ bool same_direction = true;
+ for(unsigned i=0; (same_direction && i<2); ++i)
+ for(unsigned j=i+1; (same_direction && j<3); ++j)
+ {
+ TouchPoint &pi = points[i];
+ TouchPoint &pj = points[j];
+ same_direction = ((pi.x*pj.x+pi.y*pj.y)>2*abs(pi.x*pj.y-pi.y*pj.x));
+ }
+
+ if(same_direction)
+ current_gesture = GESTURE_DRAG_3;
+ }
+ else if(points[1].down)
{
TouchPoint &p2 = points[1];
float ddx = p.down_x-p2.down_x;
/* If the points moved away from or towards each other without rotating
significantly, it's a pinch gesture. */
current_gesture = GESTURE_PINCH;
- else
- invalid_gesture = true;
+ else if(turn*turn2>0 && abs(turn)>abs(away) && abs(turn2)>abs(away2))
+ /* If the points both turned in the same direction without significant
+ changes in distance, it's a rotate gesture. */
+ current_gesture = GESTURE_ROTATE;
+ else if((p.x*p2.x+p.y*p2.y)>2*abs(p.x*p2.y-p.y*p2.x))
+ // If both points moved in the same direction, it's a two-finger drag.
+ current_gesture = GESTURE_DRAG_2;
+ }
+ else
+ current_gesture = GESTURE_DRAG;
- if(current_gesture!=GESTURE_NONE)
- {
- active_points = 3;
- set_axis_value(0, (p.down_x+p2.down_x)/2, true);
- set_axis_value(1, (p.down_y+p2.down_y)/2, true);
- }
+ if(current_gesture!=GESTURE_NONE)
+ {
+ set_gesture_location(gesture_points(current_gesture));
+ update_progress();
+ set_button_state(current_gesture, true, true);
}
else
+ invalid_gesture = true;
+}
+
+void GestureDetector::set_gesture_location(unsigned n_points)
+{
+ float x = 0;
+ float y = 0;
+ for(unsigned i=0; i<n_points; ++i)
{
- // Allow a maximum deviation of about 26° to recognize a swipe gesture.
- if(abs(p.x)>2*abs(p.y))
- current_gesture = (p.x>0 ? GESTURE_SWIPE_RIGHT : GESTURE_SWIPE_LEFT);
- else if(abs(p.y)>2*abs(p.x))
- current_gesture = (p.y>0 ? GESTURE_SWIPE_UP : GESTURE_SWIPE_DOWN);
- else
- invalid_gesture = true;
-
- if(current_gesture!=GESTURE_NONE)
- {
- active_points = 1;
- set_axis_value(0, p.down_x, true);
- set_axis_value(1, p.down_y, true);
- }
+ x += points[i].down_x;
+ y += points[i].down_y;
}
- update_progress();
+ set_axis_value(0, x/n_points, true);
+ set_axis_value(1, y/n_points, true);
+}
- if(current_gesture!=GESTURE_NONE)
- set_button_state(current_gesture, true, true);
+void GestureDetector::set_gesture_delta(unsigned n_points)
+{
+ float x = 0;
+ float y = 0;
+ for(unsigned i=0; i<n_points; ++i)
+ {
+ x += points[i].x;
+ y += points[i].y;
+ }
+
+ set_axis_value(2, x/n_points, true);
+ set_axis_value(3, y/n_points, true);
}
void GestureDetector::update_progress()
{
- TouchPoint &p = points[0];
-
- if(current_gesture==GESTURE_SWIPE_DOWN)
- set_axis_value(2, -p.y, true);
- else if(current_gesture==GESTURE_SWIPE_UP)
- set_axis_value(2, p.y, true);
- else if(current_gesture==GESTURE_SWIPE_LEFT)
- set_axis_value(2, -p.x, true);
- else if(current_gesture==GESTURE_SWIPE_RIGHT)
- set_axis_value(2, p.x, true);
- else if(current_gesture==GESTURE_PINCH)
+ if(current_gesture>=GESTURE_DRAG && current_gesture<=GESTURE_DRAG_3)
+ set_gesture_delta(gesture_points(current_gesture));
+ else if(current_gesture==GESTURE_PINCH || current_gesture==GESTURE_ROTATE)
{
+ TouchPoint &p = points[0];
TouchPoint &p2 = points[1];
/* Pinch progress is the ratio between the current distance of the points
and their distance when they were pressed. */
float ddy = p.down_y-p2.down_y;
float dx = ddx+p.x-p2.x;
float dy = ddy+p.y-p2.y;
- set_axis_value(2, sqrt(dx*dx+dy*dy)/sqrt(ddx*ddx+ddy*ddy)-1, true);
+ if(current_gesture==GESTURE_PINCH)
+ {
+ set_axis_value(2, sqrt(dx*dx+dy*dy)/sqrt(ddx*ddx+ddy*ddy)-1, true);
+ set_axis_value(3, 0, true);
+ }
+ else if(current_gesture==GESTURE_ROTATE)
+ {
+ set_axis_value(2, atan2(dy*ddx-dx*ddy, dx*ddx+dy*ddy)/M_PI/2, true);
+ set_axis_value(3, 0, true);
+ }
}
}
set_button_state(current_gesture, false, true);
set_axis_value(2, 0, false);
current_gesture = GESTURE_NONE;
- active_points = 0;
+ pending_tap = GESTURE_NONE;
}
void GestureDetector::window_resized(unsigned w, unsigned h)
down_x(0),
down_y(0),
x(0),
- y(0)
+ y(0),
+ threshold_exceeded(false)
{ }
+
+unsigned gesture_points(Gesture gesture)
+{
+ switch(gesture)
+ {
+ case GESTURE_NONE: return 0;
+ case GESTURE_TAP: return 1;
+ case GESTURE_TAP_2: return 2;
+ case GESTURE_TAP_3: return 3;
+ case GESTURE_DRAG: return 1;
+ case GESTURE_DRAG_2: return 2;
+ case GESTURE_DRAG_3: return 3;
+ case GESTURE_PINCH: return 2;
+ case GESTURE_ROTATE: return 2;
+ default: throw invalid_argument("gesture_points");
+ }
+}
+
} // namespace Input
} // namespace Msp