]> git.tdb.fi Git - libs/gui.git/commitdiff
Add a gesture detector input device
authorMikko Rasa <tdb@tdb.fi>
Fri, 19 Dec 2014 02:56:33 +0000 (04:56 +0200)
committerMikko Rasa <tdb@tdb.fi>
Fri, 19 Dec 2014 03:07:44 +0000 (05:07 +0200)
source/input/device.h
source/input/gesturedetector.cpp [new file with mode: 0644]
source/input/gesturedetector.h [new file with mode: 0644]
source/input/touchscreen.h

index e356bea86a4639cc5280d0a974eb6af3a8f005ab..7df388b18c34f5f0d2dc1001ac61e27ae5b9afa8 100644 (file)
@@ -11,7 +11,7 @@ namespace Input {
 /**
 Base class for input devices.  Input devices have two types of controls:
 buttons and axes.  Buttons are either on or off.  Axes have a floating point
-value in the range [-1, 1].
+value nominally in the range [-1, 1].
 
 Event handlers return a boolean indicating whether the event is considered
 processed.  If a handler returns true, no further handlers are invoked.
diff --git a/source/input/gesturedetector.cpp b/source/input/gesturedetector.cpp
new file mode 100644 (file)
index 0000000..c43e65a
--- /dev/null
@@ -0,0 +1,210 @@
+#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
diff --git a/source/input/gesturedetector.h b/source/input/gesturedetector.h
new file mode 100644 (file)
index 0000000..2b5ea3c
--- /dev/null
@@ -0,0 +1,67 @@
+#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
index 363111cd3ea18e5936590c12bb3b20099103c96d..99432d22309a2e889e8910793dc4c461c133fca5 100644 (file)
@@ -23,6 +23,8 @@ public:
        Touchscreen(Graphics::Window &);
        ~Touchscreen();
 
+       Graphics::Window &get_window() const { return window; }
+
        virtual std::string get_button_name(unsigned) const;
        virtual std::string get_axis_name(unsigned) const;
 private: