--- /dev/null
+#include <cmath>
+#include <sigc++/bind_return.h>
+#include "gesturedetector.h"
+#include "touchscreen.h"
+
+using namespace std;
+
+namespace Msp {
+namespace Input {
+
+GestureDetector::GestureDetector(Touchscreen &ts):
+ touchscreen(ts),
+ current_gesture(GESTURE_NONE),
+ active_points(0),
+ invalid_gesture(false)
+{
+ name = "Gesture";
+
+ touchscreen.signal_button_press.connect(sigc::bind_return(sigc::mem_fun(this, &GestureDetector::touch_down), false));
+ touchscreen.signal_button_release.connect(sigc::bind_return(sigc::mem_fun(this, &GestureDetector::touch_up), false));
+ touchscreen.signal_axis_motion.connect(sigc::bind_return(sigc::mem_fun(this, &GestureDetector::touch_move), false));
+ Graphics::Window &window = touchscreen.get_window();
+ window.signal_resize.connect(sigc::mem_fun(this, &GestureDetector::window_resized));
+ window_resized(window.get_width(), window.get_height());
+}
+
+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";
+ else if(btn==GESTURE_PINCH)
+ return "Pinch";
+ else
+ return Device::get_button_name(btn);
+}
+
+string GestureDetector::get_axis_name(unsigned axis) const
+{
+ if(axis==0)
+ return "X";
+ else if(axis==1)
+ return "Y";
+ else if(axis==2)
+ return "Progress";
+ else
+ return Device::get_axis_name(axis);
+}
+
+void GestureDetector::touch_down(unsigned btn)
+{
+ if(btn<3)
+ {
+ TouchPoint &p = points[btn];
+ p.down = true;
+ p.down_x = p.x;
+ p.down_y = p.y;
+ }
+}
+
+void GestureDetector::touch_up(unsigned btn)
+{
+ if(btn<3)
+ {
+ TouchPoint &p = points[btn];
+ p.down = false;
+
+ if(active_points&(1<<btn))
+ {
+ set_button_state(current_gesture, false, true);
+ set_axis_value(2, 0, false);
+ current_gesture = GESTURE_NONE;
+ active_points = 0;
+ invalid_gesture = true;
+ }
+
+ if(invalid_gesture)
+ {
+ invalid_gesture = false;
+ for(unsigned i=0; i<3; ++i)
+ if(points[i].down)
+ invalid_gesture = true;
+ }
+ }
+}
+
+void GestureDetector::touch_move(unsigned axis, float value, float)
+{
+ if(axis<6)
+ {
+ unsigned i = axis>>1;
+ TouchPoint &p = points[i];
+ if(axis&1)
+ p.y = value;
+ else
+ p.x = value;
+
+ if(p.down)
+ {
+ if(current_gesture==GESTURE_NONE && !invalid_gesture)
+ start_gesture();
+ else if(active_points&(1<<i))
+ update_progress();
+ }
+ }
+}
+
+void GestureDetector::start_gesture()
+{
+ TouchPoint &p = points[0];
+ if(!p.down)
+ return;
+
+ float dx = p.x-p.down_x;
+ float dy = p.y-p.down_y;
+ if(dx*dx/threshold_x_sq+dy*dy/threshold_y_sq<1)
+ return;
+
+ invalid_gesture = false;
+ if(points[1].down)
+ {
+ TouchPoint &p2 = points[1];
+ float dx2 = p2.x-p2.down_x;
+ float dy2 = p2.y-p2.down_y;
+ float ddx = p.down_x-p2.down_x;
+ float ddy = p.down_y-p2.down_y;
+ /* TODO Should the second point be also required to exceeded the
+ threshold? */
+ if(dx*dx2+dy*dy2<0 && (dx*ddx+dy*ddy)*(dx2*ddx+dy2*ddy)<0)
+ {
+ current_gesture = GESTURE_PINCH;
+ active_points = 3;
+ }
+ else
+ invalid_gesture = true;
+
+ if(current_gesture!=GESTURE_NONE)
+ {
+ set_axis_value(0, (p.down_x+p2.down_x)/2, true);
+ set_axis_value(1, (p.down_y+p2.down_y)/2, true);
+ }
+ }
+ else
+ {
+ active_points = 1;
+ if(abs(dx)>2*abs(dy))
+ current_gesture = (dx>0 ? GESTURE_SWIPE_RIGHT : GESTURE_SWIPE_LEFT);
+ else if(abs(dy)>2*abs(dx))
+ current_gesture = (dy>0 ? GESTURE_SWIPE_UP : GESTURE_SWIPE_DOWN);
+ else
+ invalid_gesture = true;
+
+ if(current_gesture!=GESTURE_NONE)
+ {
+ set_axis_value(0, p.down_x, true);
+ set_axis_value(1, p.down_y, true);
+ }
+ }
+
+ update_progress();
+
+ if(current_gesture!=GESTURE_NONE)
+ set_button_state(current_gesture, true, true);
+}
+
+void GestureDetector::update_progress()
+{
+ TouchPoint &p = points[0];
+
+ if(current_gesture==GESTURE_SWIPE_DOWN)
+ set_axis_value(2, p.down_y-p.y, true);
+ else if(current_gesture==GESTURE_SWIPE_UP)
+ set_axis_value(2, p.y-p.down_y, true);
+ else if(current_gesture==GESTURE_SWIPE_LEFT)
+ set_axis_value(2, p.down_x-p.x, true);
+ else if(current_gesture==GESTURE_SWIPE_RIGHT)
+ set_axis_value(2, p.x-p.down_x, true);
+ else if(current_gesture==GESTURE_PINCH)
+ {
+ TouchPoint &p2 = points[1];
+ float dx = p.x-p2.x;
+ float dy = p.y-p2.y;
+ float ddx = p.down_x-p2.down_x;
+ float ddy = p.down_y-p2.down_y;
+ set_axis_value(2, sqrt(dx*dx+dy*dy)/sqrt(ddx*ddx+ddy*ddy)-1, true);
+ }
+}
+
+void GestureDetector::window_resized(unsigned w, unsigned h)
+{
+ threshold_x_sq = 2500.0/(w*w);
+ threshold_y_sq = 2500.0/(h*h);
+}
+
+
+GestureDetector::TouchPoint::TouchPoint():
+ down(false),
+ down_x(0),
+ down_y(0),
+ x(0),
+ y(0)
+{ }
+
+} // namespace Input
+} // namespace Msp
--- /dev/null
+#ifndef MSP_INPUT_GESTUREDETECTOR_H_
+#define MSP_INPUT_GESTUREDETECTOR_H_
+
+#include "device.h"
+
+namespace Msp {
+namespace Input {
+
+class Touchscreen;
+
+enum Gesture
+{
+ GESTURE_NONE,
+ GESTURE_SWIPE_DOWN,
+ GESTURE_SWIPE_UP,
+ GESTURE_SWIPE_LEFT,
+ GESTURE_SWIPE_RIGHT,
+ GESTURE_PINCH
+};
+
+/**
+Interprets events from a Touchscreen as high-level gestures. One button is
+provided for each type of gesture. Axes 0 and 1 indicate the starting position
+of the gesture; axis 2 tracks its progress. The progress axis may exhibit
+absolute values greater than one.
+*/
+class GestureDetector: public Device
+{
+private:
+ struct TouchPoint
+ {
+ bool down;
+ float down_x;
+ float down_y;
+ float x;
+ float y;
+
+ TouchPoint();
+ };
+
+ Touchscreen &touchscreen;
+ TouchPoint points[3];
+ Gesture current_gesture;
+ unsigned active_points;
+ bool invalid_gesture;
+ float threshold_x_sq;
+ float threshold_y_sq;
+
+public:
+ GestureDetector(Touchscreen &);
+
+ virtual std::string get_button_name(unsigned) const;
+ virtual std::string get_axis_name(unsigned) const;
+
+private:
+ void touch_down(unsigned);
+ void touch_up(unsigned);
+ void touch_move(unsigned, float, float);
+ void start_gesture();
+ void update_progress();
+ void window_resized(unsigned, unsigned);
+};
+
+} // namespace Input
+} // namespace Msp
+
+#endif