Responding to touch input
A Cinder application can receive several touch events.
The available touch event handlers that get called by touch interaction are listed in the following table:
Method |
Usage |
---|---|
|
This is called when new touches are detected |
|
This is called when existing touches move |
|
This is called when existing touches are removed |
All of the preceding methods receive a ci::app::TouchEvent
object as a parameter with a std::vector
of ci::app::TouchEvent::Touch
objects with information about each touch detected. Since many devices can detect and respond to several touches simultaneously, it is possible and common for a touch event to contain several touches.
It is not mandatory to implement all of the preceding event handlers; you can use the ones your application requires specifically.
Cinder applications can respond to touch events on any touch-enabled device running Windows 7, OS X, or iOS.
Getting ready
Implement the necessary touch event handlers according to the touch events you want to respond to. For example, to respond to all available touch events (touches added, touches moved, and touches removed), you would need to declare and implement the following methods:
void touchesBegan( TouchEvent event ); void touchesMoved( TouchEvent event ); void touchesEnded( TouchEvent event );
How to do it…
We will learn how to work with the ci::app::TouchEvent
class to understand touch events. Perform the following steps to do so:
To access the list of touches, you can type in the following line of code:
const std::vector<TouchEvent::Touch>& touches = event.getTouches();
Iterate through the container to access each individual element.
for( std::vector<TouchEvent::Touch>::const_iterator it = touches.begin(); it != touches.end(); ++it ){ const TouchEvent::Touch& touch = *it; //do something with the touch object }
You can get the position of a touch by calling the
getPos
method that returns aVec2f
value with its position or using thegetX
andgetY
methods to receive the x and y coordinates separately, for example:for( std::vector<TouchEvent::Touch>::const_iterator it = touches.begin(); it != touches.end(); ++it ){ const TouchEvent::Touch& touch = *it; vec2f pos = touch.getPos(); float x = touch.getX(); float y = touch.getY(); }
The
getId
method returns auint32_t
value with a unique ID for thetouch
object. This ID is persistent throughout the lifecycle of the touch, which means you can use it to keep track of a specific touch as you access it on the different touch events.For example, to make an application where we draw lines using our fingers, we can create
std::map
that associates each line, in the form of aci::PolyLine<Vec2f>
object, with auint32_t
key with the unique ID of a touch.We need to include the file with
std::map
andPolyLine
to our project by adding the following code snippet to the beginning of the source file:#include "cinder/polyline.h" #include <map>
We can now declare the container:
std::map< uint32_t, PolyLine<Vec2f> > mLines;
In the
touchesBegan
method we create a new line for each detected touch and map it to the unique ID of each touch:const std::vector<TouchEvent::Touch>& touches = event.getTouches(); for( std::vector<TouchEvent::Touch>::const_iterator it = touches.begin(); it != touches.end(); ++it ){ const TouchEvent::Touch& touch = *it; mLines[ touch.getId() ] = PolyLine<Vec2f>(); }
In the
touchesMoved
method, we add the position of each touch to its corresponding line:const std::vector<TouchEvent::Touch>& touches = event.getTouches(); for( std::vector<TouchEvent::Touch>::const_iterator it = touches.begin(); it != touches.end(); ++it ){ const TouchEvent::Touch& touch = *it; mLines[ touch.getId() ].push_back( touch.getPos() ); }
In the
touchesEnded
method, we remove the line that corresponds to a touch being removed:const std::vector<TouchEvent::Touch>& touches = event.getTouches(); for( std::vector<TouchEvent::Touch>::const_iterator it = touches.begin(); it != touches.end(); ++it ){ const TouchEvent::Touch& touch = *it; mLines.erase( touch.getId() ); }
Finally, the lines can be drawn. Here we clear the background with black and draw the lines with in white. The following is the implementation of the
draw
method:gl::clear( Color::black() ); gl::color( Color::white() ); for( std::map<uint32_t, PolyLine<Vec2f> >::iterator it = mLines.begin(); it != mLines.end(); ++it ){ gl::draw( it->second ); }
The following is a screenshot of our app running after drawing some lines:
How it works…
A Cinder application responds internally to the system calls for any touch event. It will then create a ci::app::TouchEvent
object with information about the event and call the corresponding event handler in our application's class. The way to respond to touch events becomes uniform across the Windows and Mac platforms.
The ci::app::TouchEvent
class contains only one accessor method that returns a const
reference to a std::vector<TouchEvent::Touch>
container. This container has one ci::app::TouchEvent::Touch
object for each detected touch and contains information about the touch.
The ci::app::TouchEvent::Touch
object contains information about the touch including position and previous position, unique ID, the time stamp, and a pointer to the native event object which maps to UITouch
on Cocoa Touch and TOUCHPOINT
on Windows 7.
There's more...
At any time, it is also possible to get a container with all active touches by calling the getActiveTouches
method. It returns a const
reference to a std::vector<TouchEvent::Touch>
container. It offers flexibility when working with touch applications as it can be accessed outside the touch event methods.
For example, if you want to draw a solid red circle around each active touch, you can add the following code snippet to your draw
method:
const std::vector<TouchEvent::Touch>&activeTouches = getActiveTouches(); gl::color( Color( 1.0f, 0.0f, 0.0f ) ); for( std::vector<TouchEvent::Touch>::const_iterator it = activeTouches.begin(); it != activeTouches.end(); ++it ){ const TouchEvent::Touch& touch = *it; gl::drawSolidCircle( touch.getPos(), 10.0f ); }