Getting started with Multi-Touch Development in Python: Part 4

Its time for a new tutorial!  This time we will build a slightly(but only slightly) more realistic example.  We will use PyGame to render a green box, and allow us to drag it around, and resize it by pinching and squeezing.  As usual, if you have any suggestions regarding tutorials, or if you have an idea for a tutorial I should write, send me an email(can be found in the contact me section to the right), or say something in the comments.

I am going to follow the same tutorial format I used last time, where I give you the code and explain it line by line.  In this tutorial we will also take a new approach at getting touch events, through a new type of callback called a Decorator(read more about them here).  They are similar to Java’s Annotations, and are purely a way of representing this(from the first tutorial):

class Observer(object):
    def __init__(self, subject):
        subject.push_handlers(self)

class touch_up(Observer):
    def TOUCH_UP(self,blobID, xpos, ypos):
        #Do something here

class touch_down(Observer):
    def TOUCH_DOWN(self,blobID):        
        #Do something here

class touch_move(Observer):    
    def TOUCH_MOVE(self,blobID):        
        #Do something here

t = touchpy()
tu = touch_up(t)
td = touch_down(t)
tm = touch_move(t)

With this:

@t.event
def TOUCH_DOWN(blobID):    
    #Do Something

@t.event
def TOUCH_MOVE(blobID):    
    #Do Something

@t.event
def TOUCH_UP(blobID):    
    #Do Something

Not only is it _much_ easier to write, but its a lot easier to read, and actually makes sense. So, moving on to the code. I have not commented to the code, because I will be going over it line by line here, and I figured comments would make it longer then it has to be. I tried to make this as short as possible while still being readable, so its very global oriented. I would assume all tutorials from here on out will follow better practice, as they will do more complicated things(The next tutorial will either be a Picture app, or a simple button GUI, not sure yet, but I will get to both eventually). So, the code:

import sys
import math

import pygame
from pygame.locals import *

from touchpy import touch
from touchpy import utils

def make_rectlist(pos, size):    
    return (pos, (pos[0] + size, pos[1]), (pos[0] + size, pos[1] + size), (pos[0], pos[1] + size))

def get_distance(x1, y1, x2, y2):    
    return math.sqrt((y2 - y1)**2 + (x2 - x1)**2)

sz = utils.get_sz()

pygame.init()screen = pygame.display.set_mode(sz, pygame.FULLSCREEN | pygame.NOFRAME)

pos = [(x/2) - 50 for x in sz]size = 100bList = []

t = touch.touchpy()

@t.eventdef TOUCH_DOWN(blobID):    
    global bList
    bList.append(t.blobs[blobID])

@t.eventdef TOUCH_MOVE(blobID):    
    global bList, sz, size
    if len(bList) == 1:
        pos[0] += t.blobs[blobID].xmot*sz[0]
        pos[1] += t.blobs[blobID].ymot*sz[1]    
    elif len(bList) == 2:
        oDist = get_distance(bList[0].oxpos*sz[0], bList[1].oxpos*sz[0], bList[0].oypos*sz[1], bList[1].oypos*sz[1])        cDist = get_distance(bList[0].xpos*sz[0], bList[1].xpos*sz[0], bList[0].ypos*sz[1], bList[1].ypos*sz[1])

        z = round(oDist / cDist, 1)
        if z >= .5:
            size /= z

@t.event
def TOUCH_UP(blobID, xpos, ypos):    
    global bList
    bList.remove(t.blobs[blobID])

while 1:
    screen.fill((0, 0, 0))
    pygame.draw.aalines(screen, (0, 255, 0), True, make_rectlist(pos, size))
    for event in pygame.event.get():
        if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE):
            sys.exit()
    t.update()
    pygame.display.flip()

We will be skipping all the imports, because they should make sense. If they don’t I would recommend going back and reading tutorial 3 again. There is one new one though, that deserves at least a small mention.

from touchpy import utils

Since the last tutorial some changes to TouchPy have been made. Nothing that drastically changes how it operates, just some new features that make working with it a lot easier. A __init__.py file was added, so it can now be imported as a package, and a utils.py file was added. All this file does is allow you to get screen dimensions, but it does work across all platforms through a single function, which is really nice.

def make_rectlist(pos, size):
    return (pos, (pos[0] + size, pos[1]), (pos[0] + size, pos[1] + size), (pos[0], pos[1] + size))

def get_distance(x1, y1, x2, y2):    
    return math.sqrt((y2 - y1)**2 + (x2 - x1)**2)

Next we have two functions, make_rectlist() and get_distance(). make_rectlist() simply takes the position(bottom left corner) and size of a rectangle, and returns the position of each vertex(in pixels), in a four element tuple. get_distance() uses the distance formula:
sqrt{(y2-y1)^2  + (x2-x1)^2}

To calculate the distance in pixels between two points(in pixels).

sz = utils.get_sz()

This line uses that utils file we talked about earlier to get the screen size in pixels. It would be a good idea to remember this function, as it works across Linux, Mac OS, and Windows, and is very helpful for almost any application with graphics involved. get_sz() returns the size of the screen in pixels as a two element tuple, so on my system it returns (1280, 1024).

I believe I have adequately explained the next two lines, but if you do not understand them I recommend going back and reading tutorial 3.

pos = [(x/2) - 50 for x in sz]
size = 100

All this does is center the box on the screen. You do not have to worry about understanding this line, but if you do thats great! All its doing is taking each screen dimension, diving by two, and subtracting 50(half the size of the box). The same thing could be accomplished with:

pos = [sz[0] / 2 - 50, sz[1] / 2 - 50]
size = 100

Both pos and size are global variable that are constantly updated. pos holds the position of the box(bottom left corner), and size holds the length of one side(its a square). They are both mutable, because during the callbacks we update these values, then pass them through pygame.draw.lines to draw our square.

bList = []

This just defines another global, bList, which we use to hold all of the blobs that are currently alive, or have actual positions on the screen. You will see why this is useful when we get to the TOUCH_MOVE callback.

The next line instantiates the touchy objects, which you should understand pretty well by now, and if you don’t, I would recommend reading tutorials 1 and 2. Finally, we get the exciting part of the program!

Starting with the TOUCH_UP Callback:

@t.event
def TOUCH_DOWN(blobID):
    global bList
    bList.append(t.blobs[blobID])

This example uses a new callback-triggereing mechanism that I have not mentioned before. They are called decorators. I have already mentioned them, but I will again suggest you read this for more information.

Moving on to the actual contents of this we see two lines.  The first just tells Python to include the global list bList into the scope of this callback, so we can access its elements.  The next appends the blob that was touched down(t.blobs[blobID]) to bList.

The TOUCH_MOVE callback is a bit more complicated, so we will skip over that for now and go over the TOUCH_UP callback.

@t.event
def TOUCH_UP(blobID, xpos, ypos):    
    global bList    bList.remove(t.blobs[blobID])

This does the opposite of TOUCH_DOWN. It takes the blob that caused the TOUCH_UP event, and removes it from the list. We do this because we are trying to keep bList current with the blobs that are actually on the screen, and thus we must remove the blobs that are no longer on the screen.

@t.event
def TOUCH_MOVE(blobID):    
    global bList, sz, size
    if len(bList) == 1:
        pos[0] += t.blobs[blobID].xmot*sz[0]
        pos[1] += t.blobs[blobID].ymot*sz[1]    
        if len(bList) == 2:
            oDist = get_distance(bList[0].oxpos*sz[0], bList[1].oxpos*sz[0], bList[0].oypos*sz[1], bList[1].oypos*sz[1])
            cDist = get_distance(bList[0].xpos*sz[0], bList[1].xpos*sz[0], bList[0].ypos*sz[1], bList[1].ypos*sz[1])

            z = round(oDist / cDist, 1)        
            if z >= .5:
                size /= z

In this callback we decide whether the user wants to resize, or move the box. If there is one blob then it is assumed that they want to move the box, which is done by adding the relative movement of the blob to the position of the box.  If there are two fingers on the table then it is assumed that they would like to resize the box.

        oDist = get_distance(bList[0].oxpos*sz[0], bList[1].oxpos*sz[0], bList[0].oypos*sz[1], bList[1].oypos*sz[1])

In this line we get the distance between the old position of the blob(oxpos, oypos) in pixels using the distance formula. In the next line we do the same with the current position. Next we get the ratio of change between the two, and apply this to the zoom. This achieves the effect of zooming in. This is not the best way to do it, as you will notice it is a bit jerky, and will jump around at times. In a future tutorial I will present some better ways of zooming.

while 1:
    screen.fill((0, 0, 0))
    pygame.draw.aalines(screen, (0, 255, 0), True, make_rectlist(pos, size))    
    for event in pygame.event.get():        
        if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE):
    sys.exit()
    t.update()
    pygame.display.flip()

Next we have the main program loop. This is pretty simple. Clear the display, draw the box, see if the user wants to quit(hit the escape key or get QUIT event from OS), updates touchpy, then flips PyGame’s display.

That concludes this tutorial!  As always, if you have any suggestions or ideas for a new tutorial I am happyto hear them!  I don’t really know where to go from here with the tutorials, I think I will start with something useful, or maybe kinetic scrolling, who knows…  I would love to hear from you and see what you have to think!

Recommended resources for further reading:

2 comments so far

  1. Sharath on

    Thanks for the Great tutorials

  2. JSearles on

    You’ve got me leaning heavily toward Python after looking at Flash, C# Processing and others. The line by line explanations are the key. Please don’t stop the tutorials. It’s easy for you to assume that some subject may be too elementary, but in reality noobs like me would eat them up. My problem is that I don’t know what I don’t know. The only thing I can suggest is that you look at some of the more popular MT apps and pick some exciting techniques as the target for future tutorials. Techniques and e3ffects like smoke, blur, perspective, object movement, objects tracking other objects or a finger.

    Thanks for what you have already done. I intend to get started this weekend (I hope) now that my table is almost ready.


Leave a comment