Just about every project that I write uses my Touch Controller. So if you want to use or understand one of my Codea projects, it is probably a good idea to understand how this one works.
To do that, it is first a good idea to understand the problem that it is designed to solve, and to understand that, one needs to first recall how a Codea program runs. Codea first runs the
setup function, then it enters a loop where it runs the
draw function interspersed with the
touched function as needed. That is, in each loop the
draw function is run once and then the
touched function is called for each new touch event. This means that the
touched function can be run several times or not at all, and if a touch doesn't do anything new in the current loop then the
touched function is not run for that touch even though it may still be "active".
So doing stuff in the
touched function is dangerous because it can be called an unpredictable number of times, including none at all.
Also, touch events are essentially independent pieces of information with only the
touch.id to link a new event to previous touch events.
The touch controller, therefore, is designed to do two things:
Keep track of which object in Codea is associated to a particular touch.
This is done as follows. When a new touch – i.e. one with state
BEGAN– is registered, every known object is queried to see if it claims the touch. The first to do so is assigned that touch and all of its updates.
"Smooth out" the incoming touch information so that each object can deal with all of its touches in one go and on every draw.
To do this, the touch controller maintains a list of all the touches associated with each object and once per
drawcycle it passes all of an object's touches – whether updated with new information or not – to it as a single collection.
This means that each object knows that its touch handling function will be called exactly once per
draw cycle whenever it is "being touched".
2 Initial Structure
The touch controller needs to maintain a list of "touchable objects". Therefore, each object that wants to be able to accept touches needs to register itself with the touch controller. The touch controller interacts with these objects in two ways. The first interaction comes when a new touch comes in. Then the controller must query each of its objects to see which claims it. It does this by calling the method
isTouchedBy which must return
false. The second interaction comes when it passes all the touches to an object. It does this by calling the method
Thus the structure of a program that uses this touch controller must be as follows. We set up the touch controller in the
touches = Touches()
Then each object that wants to claim touches registers itself with the controller via:
unshiftHandler for putting oneself at the front of the list).
The objects must have methods
processTouches. The first of these takes a
touch as input. The touches are bundled into a gesture before being passed to
processTouches. More on that in a moment.
The final pieces in the main program are that the touch controller must claim every touch for itself via:
function touched(t) touches:addTouch(t) end
and at the beginning of the
draw function, it must run its processing code:
function draw() touches:draw()
The touches associated with an object are passed to it as a single collection once every
draw cycle. Technically, the touches are first augmented so that they contain more information than just the latest update. Then, the touches are bundled into a
Gesture which is passed to the receiving object. Before being passed on, the gesture is analysed to establish some basic information.
Touch object knows the following information (plus some internal stuff):
||The touch identifier|
||The current inbuilt touch corresponding to this extended object|
||The first touch|
||Was there new information for this touch?|
||Last time that new information was given|
||The time this object was created|
||The second time new information was given|
||The time between the latest updates|
||What happened the time before|
||Has this touch moved?|
||Was there a long delay at the start?|
||Did it end very soon after it began?|
||Should we prolong the life of this object?|
It is also possible to compute its velocity, either instantaneous, recent, or mean.
Gesture knows the following information:
||Number of touches|
||Have any of its touches been updated?|
||The time of the last information|
||Have all the touches ended?|
||Have all the touches ended at least half a second ago?|
||Is this a tap (no touches have moved)?|
||Is this a long gesture?|
||Is this a short gesture?|
||If a multi-touch gesture, is it a pinch?|
The touches themselves are contained in both the table
touches and the array (numerically indexed table)
The gesture is passed to the object's
processTouches method. One thing that that method usually ought to do is call the
noted method on the gesture. This resets the
updated flag on the gesture and all of its touches. It is up to the object to do this.
One subtlety worth mentioning is the distinction between a gesture being
ended and being
finished. A gesture lives on beyond the lifespan of each of its touches by half a second. This means that if a new touch starts within half a second of a previous one, it is counted as being part of the same gesture. The reason for this is to allow for a gesture to consist of multiple taps and similar complicated sequences. If a gesture is
ended then all of its touches are in the state
ENDED, but if it is
finished then that has been true for half a second. Once a gesture reaches the state
finished then the touch controller resets it. However, an object can reset it early by calling the
reset method on the gesture.
4 Example Code
An example project, containing the touch controller code, can be found on github. This project has two touchable classes. The first,
Stuff, will display information about the
Gesture when it is touched. The second,
Box, is simply a box that can be touched and will change colour when it is being touched. Note that this shows the advantage of the fact that the
processTouches method of the
Box class being called exactly once per
draw cycle while it is being touched since we can update the colour in the
processTouches function without worrying about it being called the wrong number of times.