Touch User Interface (Python on Symbian)
From Symbian Developer Community
Original Author: Pankaj Nathani
This chapter explains how to write touch-aware applications in Python and how to support touch and non-touch devices in the same script.
Contents |
Introduction
Touch-enabled user interfaces allow interaction with devices in a more natural way than is possible using just hardware buttons or a keypad. While some applications can work meaningfully with both types of interfaces, many applications are improved or are only really feasible on touch enabled devices.
The Symbian platform supports devices both with and without touch (devices based on the same code as the Symbian platform have supported touch since 1997!). At time of writing, popular "touch" devices include the Nokia 5800 XpressMusic, Nokia N97, Samsung i8910 HD, Sony Ericsson Satio, to name just a few!
This chapter explains how to detect whether touch is supported on the device, how to handle touch events from different regions of the screen in your canvas, and how Python will handle the case where overlapping touch screen event handlers have been declared. It is accompanied by a number of worked example applications and games that demonstrate how to create your own custom touch-enabled UI elements.
Supporting both touch and non-touch devices
Python applications that use only the Basic User Interface elements (e.g. notifications, list boxes, dialogs etc) should run on touch enabled devices unchanged; events generated by the user touching UI elements are mapped to the same events that the application would have got from physical key presses.
Python on Symbian 2.0 provides explicit touch pointer event support to the appuifw module's Canvas object. Applications that use a canvas for drawing can easily be made to just "work sensibly" on both touch and non-touch devices using an on-screen virtual directional keypad to simulate the action of the physical keypad, as discussed in the chapter on Graphics and Multimedia. The example below shows how the virtual keypad might be enabled on touch devices only.
import appuifw
...
if appuifw.touch_enabled():
appuifw.app.directional_pad=true;
else:
appuifw.app.directional_pad=false;
...
The code above uses the touch_enabled() function to determine if the device is touch enabled - it returns True if touch UI is supported and False if not.
Applications that support custom touch-enabled Canvas elements can use this method to conditionally enable the code for touch devices.
Detecting touch events on full screen
Python can detect 4 different touch events, which are referenced using key codes below (from key_codes.py):
- EButton1Down - Touch down event, sent when finger/stylus touches the screen
- EButton1Up- Touch up event, sent when finger/stylus is lifted from the screen
- EDrag– Drag event, sent when finger/stylus is dragged on the screen
- ESwitchOn- Tap screen event, sent when finger/stylus taps or double clicks the screen
In order to detect touch events, the Canvas binds an event handler to the required key codes; this handler is then called whenever the specified event occurs. This is much the same approach used to bind key presses to the Canvas, as discussed in the chapter on Graphics and Multimedia.
The code snipped below shows an application that will display a notification whenever the screen is touched. In this case the canvas binds to the EButton1Down event, bind to other touch events by replacing EButton1Down with EButton1Up , EDrag or ESwitchOn.
import appuifw
import graphics
import e32
import key_codes #import key_codes - required for touch event detection
# define white colour constant
RGB_WHITE =(255, 255, 255)
# change application screen size to 'full'
appuifw.app.screen = 'full'
def quit():
'''Define quit function'''
app_lock.signal()
def down_event(event):
'''Pen DOWN event handler'''
appuifw.note(u"Down Event")
#prepare canvas for drawing
canvas = appuifw.Canvas()
appuifw.app.body = canvas
#define right soft key as exit key
appuifw.app.exit_key_handler = quit
#clear canvas with white colour
canvas.clear(RGB_WHITE)
'''bind canvas for touch event'''
canvas.bind(key_codes.EButton1Down, down_event)
#EButton1Up, EDrag and ESwitchOn could be used similarly
#Wait for user to exit
app_lock = e32.Ao_lock()
app_lock.wait()
In the above snippet, the EButton1Down event binds to the down_event() function. The call back function can be replaced by calling the bind() function again with a different handler, or cleared by specifying None as shown:
canvas.bind(key_codes.EButton1Down, None)
Detecting touch on a specific area
In the previous section, we showed how touch events could be detected on the full canvas. Detecting events on a specific area of the canvas is achieved by passing the top left and bottom right co-ordinates of the required region as additional parameters to the bind() call.
Many UI elements have different shapes - for example buttons are often circular or rounded. At the end of this section we explain how you can detect touch events in arbitrarily shaped regions, even though bind() we can only specify rectangular areas.
Detecting a touch event within a rectangle
In order to detect a touch down event in a rectangle with top left corner co-ordinates (x1, y1) and bottom right corner co-ordinates (x2, y2), we would use:
canvas.bind(key_codes.EButton1Down, down_event, ((x1,y1),(x2,y2)))
The code snippet below illustrates how we detect each of the different touch events in a different area of the canvas/screen:
'''Detecting touch over a specific area of screen'''
import appuifw
import graphics
import e32
import key_codes #import key_codes - required for touch event detection
# define colour constants
RGB_RED = (255, 0, 0)
RGB_GREEN = (0, 255, 0)
RGB_BLUE = (0, 0, 255)
RGB_PURPLE = (100,0,255)
RGB_BLACK = (0,0,0)
# disable directional pad
appuifw.app.directional_pad = False
#prepare canvas for drawing
canvas = appuifw.Canvas()
appuifw.app.body = canvas
#obtaining canvas size (Total_x and Total_y)
Total_x, Total_y = canvas.size
y1 = Total_y/4
def blue_down(event):
''' Blue DOWN event handler '''
appuifw.note(u"Blue Down")
def green_up(event):
''' Green UP event handler '''
appuifw.note(u"Green Up")
def red_drag(event):
''' Red DRAG event handler '''
appuifw.note(u"Red Drag")
def purple_tap(event):
''' Purple TAP event handler '''
appuifw.note(u"Purple Tap")
# Blue rectangle - DOWN Event
'''Draw Blue rectangle and text'''
canvas.rectangle(((0,0), (Total_x,y1)), fill=RGB_BLUE, width=5)
canvas.text((Total_x/2,y1/2), u"DOWN", fill = RGB_BLACK,font=(u'Nokia Hindi S60',40,appuifw.STYLE_BOLD))
'''Bind DOWN to Blue rectangle'''
canvas.bind(key_codes.EButton1Down, blue_down, ((0,0), (Total_x,y1)))
# Green rectangle - UP Event
'''Draw Green rectangle and text'''
canvas.rectangle(((0,y1), (Total_x,2*y1)), fill=RGB_GREEN, width=5)
canvas.text((Total_x/2,3*y1/2), u"UP", fill = RGB_BLACK,font=(u'Nokia Hindi S60',40,appuifw.STYLE_BOLD))
'''Bind UP to Blue rectangle'''
canvas.bind(key_codes.EButton1Up, green_up, ((0,y1), (Total_x,2*y1)))
# Red rectangle - DRAG Event
'''Draw Red rectangle and text'''
canvas.rectangle(((0,2*y1), (Total_x,3*y1)), fill=RGB_RED, width=5)
canvas.text((Total_x/2,5*y1/2), u"DRAG", fill = RGB_BLACK,font=(u'Nokia Hindi S60',40,appuifw.STYLE_BOLD))
'''Bind DRAG to Red rectangle'''
canvas.bind(key_codes.EDrag, red_drag, ((0,2*y1), (Total_x,3*y1)))
# Purple rectangle - TAP Event
'''Draw Purple rectangle and text'''
canvas.rectangle(((0,3*y1), (Total_x,4*y1)), fill=RGB_PURPLE, width=5)
canvas.text((Total_x/2,7*y1/2), u"TAP", fill = RGB_BLACK,font=(u'Nokia Hindi S60',40,appuifw.STYLE_BOLD))
'''Bind TAP to Red rectangle'''
canvas.bind(key_codes.ESwitchOn, purple_tap, ((0,3*y1), (Total_x,4*y1)))
#wait for user to exit
app_lock = e32.Ao_lock()
app_lock.wait()
When run, the example application looks like the screenshot below. Each area of the screen responds to its specified type of event by displaying a note.
Piano example (overlapping events)
Multiple events may bind() to the same area of the canvas/screen. In this case, the event handler that is called is the callback that was registered last. Consider the following examples:
For Overlapping 1,
- Touching on point 1 would trigger callback registered to the red rectangle.
- Touching on point 3 would trigger callback registered to the blue rectangle.
- Touching on point 2 (where overlapping has occurred) would trigger callback registered to the blue rectangle.
For Overlapping 2,
- Touching on point 1 would trigger callback registered to the red rectangle.
- Touching on point 2 (where overlapping has occurred) would trigger callback registered to the blue rectangle.
This is further illustrated with the example of a touch Piano:
The back keys overlap the white keys. To ensure that touching the overlapping area (black keys) triggers the callback functions registered to the respective black keys, we bind their event handlers last.
import appuifw
import graphics
import e32
import key_codes #import key_codes - required for touch event detection
import audio
# define white colour constants
RGB_WHITE =(255, 255, 255)
RGB_BLACK =(0, 0, 0)
# define sound files
white1_sound= u"C:\\Data\\Sounds\\Digital\\white1.mp3"
white2_sound= u"C:\\Data\\Sounds\\Digital\\white2.mp3"
white3_sound= u"C:\\Data\\Sounds\\Digital\\white3.mp3"
white4_sound= u"C:\\Data\\Sounds\\Digital\\white4.mp3"
white5_sound= u"C:\\Data\\Sounds\\Digital\\white5.mp3"
white6_sound= u"C:\\Data\\Sounds\\Digital\\white6.mp3"
black1_sound= u"C:\\Data\\Sounds\\Digital\\black1.mp3"
black2_sound= u"C:\\Data\\Sounds\\Digital\\black2.mp3"
black3_sound= u"C:\\Data\\Sounds\\Digital\\black3.mp3"
black4_sound= u"C:\\Data\\Sounds\\Digital\\black4.mp3"
# change application screen size to 'full'
appuifw.app.screen = 'full'
# disable directional pad
appuifw.app.directional_pad = False
def quit():
'''Define quit function'''
app_lock.signal()
appuifw.app.exit_key_handler = quit
def white1(event):
'''White1 callback function'''
#The path of the file that is to be played when white1 is touched
s1 = audio.Sound.open(white2_sound)
try: # Try to stop() - the file may be previously playing
s1.stop()
s1.play()
except:
s1.play()
def white2(event):
'''White2 callback function'''
#The path of the file that is to be played when white1 is touched
s2 = audio.Sound.open(white2_sound)
try: # Try to stop() - the file may be previously playing
s2.stop()
s2.play()
except:
s2.play()
def white3(event):
'''White3 callback function'''
#The path of the file that is to be played when white1 is touched
s3 = audio.Sound.open(white3_sound)
try: # Try to stop() - the file may be previously playing
s3.stop()
s3.play()
except:
s3.play()
def white4(event):
'''White4 callback function'''
#The path of the file that is to be played when white1 is touched
s4 = audio.Sound.open(white4_sound)
try: # Try to stop() - the file may be previously playing
s4.stop()
s4.play()
except:
s4.play()
def white5(event):
'''White5 callback function'''
#The path of the file that is to be played when white1 is touched
s5 = audio.Sound.open(white5_sound)
try: # Try to stop() - the file may be previously playing
s5.stop()
s5.play()
except:
s5.play()
def white6(event):
'''White6 callback function'''
#The path of the file that is to be played when white1 is touched
s6 = audio.Sound.open(white6_sound)
try: # Try to stop() - the file may be previously playing
s6.stop()
s6.play()
except:
s6.play()
def black1(event):
'''Black1 callback function'''
#The path of the file that is to be played when white1 is touched
s7 = audio.Sound.open(black1_sound)
try: # Try to stop() - the file may be previously playing
s7.stop()
s7.play()
except:
s7.play()
def black2(event):
'''Black2 callback function'''
#The path of the file that is to be played when white1 is touched
s8 = audio.Sound.open(black2_sound)
try: # Try to stop() - the file may be previously playing
s8.stop()
s8.play()
except:
s8.play()
def black3(event):
'''Black3 callback function'''
#The path of the file that is to be played when white1 is touched
s9 = audio.Sound.open(black3_sound)
try: # Try to stop() - the file may be previously playing
s9.stop()
s9.play()
except:
s9.play()
def black4(event):
'''Black4 callback function'''
#The path of the file that is to be played when white1 is touched
s10 = audio.Sound.open(black4_sound)
try: # Try to stop() - the file may be previously playing
s10.stop()
s10.play()
except:
s10.play()
#prepare canvas for drawing
canvas = appuifw.Canvas()
appuifw.app.body = canvas
#define right soft key as exit key
appuifw.app.exit_key_handler = quit
Total_x, Total_y = canvas.size
y1 = Total_y/6
#clear canvas with white colour
canvas.clear(RGB_WHITE)
#White Key #1 - Draw and bind touch Pen down event
canvas.rectangle(((0,0), (Total_x,y1)), outline=RGB_BLACK, fill=RGB_WHITE, width=3)
canvas.bind(key_codes.EButton1Down, white1, ((0,0), (Total_x,y1)))
#White Key #2 - Draw and bind touch Pen down event
canvas.rectangle(((0,y1), (Total_x,2*y1)),outline=RGB_BLACK, fill=RGB_WHITE, width=3)
canvas.bind(key_codes.EButton1Down, white2, ((0,y1), (Total_x,2*y1)))
#White Key #3 - Draw and bind touch Pen down event
canvas.rectangle(((0,2*y1), (Total_x,3*y1)), outline=RGB_BLACK, fill=RGB_WHITE, width=3)
canvas.bind(key_codes.EButton1Down, white3, ((0,y1), (Total_x,2*y1)))
#White Key #4 - Draw and bind touch Pen down event
canvas.rectangle(((0,3*y1), (Total_x,4*y1)), outline=RGB_BLACK,fill=RGB_WHITE, width=3)
canvas.bind(key_codes.EButton1Down, white4, ((0,3*y1), (Total_x,4*y1)))
#White Key #5 - Draw and bind touch Pen down event
canvas.rectangle(((0,4*y1), (Total_x,5*y1)), outline=RGB_BLACK,fill=RGB_WHITE, width=3)
canvas.bind(key_codes.EButton1Down, white5, ((0,4*y1), (Total_x,5*y1)))
#White Key #6 - Draw and bind touch Pen down event
canvas.rectangle(((0,5*y1), (Total_x,6*y1)), outline=RGB_BLACK,fill=RGB_WHITE, width=3)
canvas.bind(key_codes.EButton1Down, white6, ((0,5*y1), (Total_x,6*y1)))
#Black Key #1 - Draw and bind touch Pen down event
canvas.rectangle(((0,y1-30), (Total_x/2,y1+30)), fill=RGB_BLACK, width=3)
canvas.bind(key_codes.EButton1Down, black1, ((0,y1-30), (Total_x/2,y1+30)))
#Black Key #2 - Draw and bind touch Pen down event
canvas.rectangle(((0,2*y1-30), (Total_x/2,2*y1+30)), fill=RGB_BLACK, width=3)
canvas.bind(key_codes.EButton1Down, black2, ((0,2*y1-30), (Total_x/2,2*y1+30)))
#Black Key #3 - Draw and bind touch Pen down event
canvas.rectangle(((0,4*y1-30), (Total_x/2,4*y1+30)), fill=RGB_BLACK, width=3)
canvas.bind(key_codes.EButton1Down, black3, ((0,4*y1-30), (Total_x/2,4*y1+30)))
#Black Key #4 - Draw and bind touch Pen down event
canvas.rectangle(((0,5*y1-30), (Total_x/2,5*y1+30)), fill=RGB_BLACK, width=3)
canvas.bind(key_codes.EButton1Down, black4, ((0,5*y1-30), (Total_x/2,5*y1+30)))
#Wait for user to exit
app_lock = e32.Ao_lock()
app_lock.wait()
Detecting touch events in arbitrary regions
The bind() function lets us register for interest in touch events that occur in a rectangular area. But what do we do when we want to use other shapes - for example buttons that have rounded edges, or some other arbitrary shape?
There are three basic approaches we can use. We can approximate the area using a single rectangle (not very nice, but might be acceptable for a slightly rounded rectangular button), we build up the required polygon using a number of overlapping or adjacent rectangles, or we can detect events in the bounding rectangle of our shape, and then determine whether the touch event was inside the shape in our callback.
The following code snippet demonstrates the third approach, using the example of a circular area in which we want to detect touch events.
import appuifw
import graphics
import e32
import key_codes #import key_codes - required for touch event detection
import math
# define colour constants
RGB_BLUE = (0, 0, 255)
# disable directional pad
appuifw.app.directional_pad = False
#prepare canvas for drawing
canvas = appuifw.Canvas()
appuifw.app.body = canvas
#obtaining canvas size (Total_x and Total_y)
Total_x, Total_y = canvas.size
# define circle centre co-ordinates and radius
circle_x=Total_x/2
circle_y=Total_y/2
radius=100
def calculate(centre, radius):
'''Function to calculate co-ordinates of the circle'''
return ((centre[0]-radius, centre[1]-radius), (centre[0]+radius, centre[1]+radius))
def blue_down(pos):
''' Blue DOWN event handler '''
global circle_x,circle_y, radius
# Use the 'distance formula' to check if the touched position is within the circle
distance=math.hypot(pos[0]-circle_x, pos[1]-circle_y)
if distance<=radius:
appuifw.note(u"Circle touched")
# Blue rectangle - DOWN Event
'''Draw Blue rectangle and text'''
canvas.ellipse((calculate((circle_x, circle_y), radius)), fill=RGB_BLUE)
canvas.bind(key_codes.EButton1Down, blue_down)
#wait for user to exit
app_lock = e32.Ao_lock()
app_lock.wait()
The blue_down(pos) function is called if a touch event is called anywhere within the bounding rectangle of the circle. The function then uses the position of the touch and the radius of the circle to determine whether the touch occured inside the circle, and if so, shows a note.
The images below show the code above in action.
Painting with touch!
Using what we learned in this chapter and in the earlier chapters, let’s make a simple application similar to paint on windows.
First we prepare a blank canvas (as in the previous examples!). Then we draw a point where we detect any touch event on the canvas. Since we need to detect more than one event type on the same area (full canvas) – we use the default event_callback function of the canvas instead of binding to a single event.
canvas = appuifw.Canvas(event_callback=handle_event, redraw_callback=handle_redraw)
appuifw.app.body = canvas
appuifw.app.exit_key_handler = quit
Whenever canvas is touched, handle_event is called, with the event information passed as an argument. The event is a dictionary containing the details related to the pointer event. It has the format (remains unchanged for non-touch devices):
- 'type': one of the several pointer events - EButton1Down, EButton1Up and EDrag and ESwitchOn
- 'modifiers': the modifiers that apply to this pointer event
- ‘pos’: A tuple containing the x-y pointer co-ordinates
In the handle_event function we detect the touch event and draw a point:
def handle_event(event):
if event['type'] not in (key_codes.EButton1Up, key_codes.EButton1Down,key_codes.EDrag):
return
canvas.point((event['pos'][0], event['pos'][1]),outline=RGB_RED,width=10)
This first implementation of the paint application did not quite work as expected - we didn't get an event at every point so we didn't draw a continuous line.
In order to fix this we modify the handle_event function as shown below. This simply ensures that for a EDrag event we draw a line from the previous touched co-ordinated to the dragged co-ordinates. We also draw 2 points when we detect touch to improve the resolution of drawing on the screen.
def handle_event(event):
if event['type'] not in (key_codes.EButton1Up, key_codes.EButton1Down,key_codes.EDrag):
return
if event['type'] in (key_codes.EButton1Down, key_codes.EButton1Up):
canvas.point((event['pos'][0], event['pos'][1]),outline=RGB_RED,width=10)
canvas.point((event['pos'][0], event['pos'][1]),outline=RGB_RED,width=10)
prev_pos[0]=event['pos'][0]
prev_pos[1]=event['pos'][1]
#appuifw.note(u"Green Down")
elif event['type'] ==key_codes.EDrag:
rect=(prev_pos[0], prev_pos[1],event['pos'][0], event['pos'][1])
canvas.line(rect, outline=RGB_RED, width=10,fill=RGB_RED)
prev_pos[0]=event['pos'][0]
prev_pos[1]=event['pos'][1]
The complete code of the paint application and obligatory screen show are given below:
import appuifw
import graphics
import e32
import key_codes #import key_codes - required for touch event detection
# define colour constants
RGB_RED = (255, 0, 0)
RGB_GREEN = (0, 255, 0)
RGB_BLUE = (0, 0, 255)
RGB_WHITE =(255, 255, 255)
canvas=None
global prev_pos
prev_pos=[0,0]
# change application screen size to 'full'
appuifw.app.screen = 'full'
def handle_redraw(event):
'''Define redraw function'''
pass
def handle_event(event):
'''Define event callback function'''
if event['type'] not in (key_codes.EButton1Up, key_codes.EButton1Down,key_codes.EDrag):
return
if event['type'] in (key_codes.EButton1Down, key_codes.EButton1Up):
canvas.point((event['pos'][0], event['pos'][1]),outline=RGB_RED,width=10)
canvas.point((event['pos'][0], event['pos'][1]),outline=RGB_RED,width=10)
prev_pos[0]=event['pos'][0]
prev_pos[1]=event['pos'][1]
if event['type'] ==key_codes.EDrag:
rect=(prev_pos[0], prev_pos[1],event['pos'][0], event['pos'][1])
canvas.line(rect, outline=RGB_RED, width=10,fill=RGB_RED)
prev_pos[0]=event['pos'][0]
prev_pos[1]=event['pos'][1]
canvas = appuifw.Canvas(event_callback=handle_event, redraw_callback=handle_redraw)
appuifw.app.body = canvas
appuifw.app.exit_key_handler = quit
# Clear the canvas
canvas.clear(RGB_WHITE)
app_lock = e32.Ao_lock()
app_lock.wait()
Touchy Tic-Tac-Toc
We finish up this chapter with a quick and easy game –Tic-Tac-Toe!
import appuifw
import graphics
import e32
import key_codes #import key_codes - required for touch event detection
# define colour constants
RGB_RED = (255, 0, 0)
RGB_GREEN = (0, 255, 0)
RGB_BLUE = (0, 0, 255)
RGB_BLACK=(0,0,0)
# define board colour
board_colour=RGB_BLUE
# define flag variables
turn_flag="Cross"
flag_rect1=None
flag_rect2=None
flag_rect3=None
flag_rect4=None
flag_rect5=None
flag_rect6=None
flag_rect7=None
flag_rect8=None
flag_rect9=None
def handle_redraw(event):
pass
# disable directional pad
appuifw.app.directional_pad = False
canvas = appuifw.Canvas(redraw_callback=handle_redraw)
appuifw.app.body = canvas
Total_x, Total_y = canvas.size
RectWidth=Total_x/3
RectHeight=Total_y/3
print RectWidth,RectHeight
def draw_cross(pos, cross_colour):
'''Function to draw a cross - on the co-ordinates passed as arguments'''
cross_length=30
cross_width=15
a=pos[0]-cross_length
b=pos[1]-cross_length
while a<=pos[0]+cross_length:
canvas.point((a,b), width=cross_width, outline=cross_colour)
a+=1
b+=1
a=pos[0]+cross_length
b=pos[1]-cross_length
while a>=pos[0]-cross_length and b<=pos[1]+cross_length:
canvas.point((a,b), width=cross_width, outline=cross_colour)
a-=1
b+=1
def check_winner():
'''Function to check the winner after each turn'''
global flag_rect1,flag_rect2,flag_rect3,flag_rect4,flag_rect5,flag_rect6,flag_rect7,flag_rect8,flag_rect9
if flag_rect1=="Cross" and flag_rect2=="Cross" and flag_rect3=="Cross":
appuifw.note(u"Cross wins!")
elif flag_rect1=="Zero" and flag_rect2=="Zero" and flag_rect3=="Zero":
appuifw.note(u"Zero wins!")
elif flag_rect1=="Cross" and flag_rect4=="Cross" and flag_rect7=="Cross":
appuifw.note(u"Cross wins!")
elif flag_rect1=="Zero" and flag_rect4=="Zero" and flag_rect7=="Zero":
appuifw.note(u"Zero wins!")
elif flag_rect1=="Cross" and flag_rect5=="Cross" and flag_rect9=="Cross":
appuifw.note(u"Cross wins!")
elif flag_rect1=="Zero" and flag_rect5=="Zero" and flag_rect9=="Zero":
appuifw.note(u"Zero wins!")
elif flag_rect2=="Cross" and flag_rect5=="Cross" and flag_rect8=="Cross":
appuifw.note(u"Cross wins!")
elif flag_rect2=="Zero" and flag_rect5=="Zero" and flag_rect8=="Zero":
appuifw.note(u"Zero wins!")
elif flag_rect3=="Cross" and flag_rect6=="Cross" and flag_rect9=="Cross":
appuifw.note(u"Cross wins!")
elif flag_rect3=="Zero" and flag_rect6=="Zero" and flag_rect9=="Zero":
appuifw.note(u"Zero wins!")
elif flag_rect3=="Cross" and flag_rect5=="Cross" and flag_rect7=="Cross":
appuifw.note(u"Cross wins!")
elif flag_rect3=="Zero" and flag_rect5=="Zero" and flag_rect7=="Zero":
appuifw.note(u"Zero wins!")
elif flag_rect1=="Cross" and flag_rect2=="Cross" and flag_rect3=="Cross":
appuifw.note(u"Cross wins!")
elif flag_rect1=="Zero" and flag_rect2=="Zero" and flag_rect3=="Zero":
appuifw.note(u"Zero wins!")
elif flag_rect4=="Cross" and flag_rect5=="Cross" and flag_rect6=="Cross":
appuifw.note(u"Cross wins!")
elif flag_rect4=="Zero" and flag_rect5=="Zero" and flag_rect6=="Zero":
appuifw.note(u"Zero wins!")
elif flag_rect7=="Cross" and flag_rect8=="Cross" and flag_rect9=="Cross":
appuifw.note(u"Cross wins!")
elif flag_rect7=="Zero" and flag_rect8=="Zero" and flag_rect9=="Zero":
appuifw.note(u"Zero wins!")
def down(pos):
''' Event handler '''
global turn_flag, flag_rect1,flag_rect2,flag_rect3,flag_rect4,flag_rect5,flag_rect6,flag_rect7,flag_rect8,flag_rect9
if 0<pos[0]<RectWidth and 0<pos[1]<RectHeight:
if flag_rect1 not in ("Cross", "Zero"):
if turn_flag=="Cross":
draw_cross(pos, RGB_GREEN)
flag_rect1="Cross"
turn_flag="Zero"
else:
canvas.point(pos,0x00ff00,width=50)
flag_rect1="Zero"
turn_flag="Cross"
check_winner()
elif RectWidth<pos[0]<2*RectWidth and 0<pos[1]<RectHeight:
if flag_rect2 not in ("Cross", "Zero"):
if turn_flag=="Cross":
draw_cross(pos, RGB_GREEN)
flag_rect2="Cross"
turn_flag="Zero"
else:
canvas.point(pos,0x00ff00,width=50)
flag_rect2="Zero"
turn_flag="Cross"
check_winner()
elif 2*RectWidth<pos[0]<3*RectWidth and 0<pos[1]<RectHeight:
if flag_rect3 not in ("Cross", "Zero"):
if turn_flag=="Cross":
draw_cross(pos, RGB_GREEN)
flag_rect3="Cross"
turn_flag="Zero"
else:
canvas.point(pos,0x00ff00,width=50)
flag_rect3="Zero"
turn_flag="Cross"
check_winner()
elif 0<pos[0]<RectWidth and RectHeight<pos[1]<2*RectHeight:
if flag_rect4 not in ("Cross", "Zero"):
if turn_flag=="Cross":
draw_cross(pos, RGB_GREEN)
flag_rect4="Cross"
turn_flag="Zero"
else:
canvas.point(pos,0x00ff00,width=50)
flag_rect4="Zero"
turn_flag="Cross"
check_winner()
print turn_flag, flag_rect4
elif RectWidth<pos[0]<(2*RectWidth) and RectHeight<pos[1]<2*RectHeight:
if flag_rect5 not in ("Cross", "Zero"):
if turn_flag=="Cross":
draw_cross(pos, RGB_GREEN)
flag_rect5="Cross"
turn_flag="Zero"
else:
canvas.point(pos,0x00ff00,width=50)
flag_rect5="Zero"
turn_flag="Cross"
check_winner()
elif 2*RectWidth<pos[0]<3*RectWidth and RectWidth<pos[1]<2*RectHeight:
if flag_rect6 not in ("Cross", "Zero"):
if turn_flag=="Cross":
draw_cross(pos, RGB_GREEN)
flag_rect6="Cross"
turn_flag="Zero"
else:
canvas.point(pos,0x00ff00,width=50)
flag_rect6="Zero"
turn_flag="Cross"
check_winner()
elif 0<pos[0]<RectWidth and 2*RectHeight<pos[1]<3*RectHeight:
if flag_rect7 not in ("Cross", "Zero"):
if turn_flag=="Cross":
draw_cross(pos, RGB_GREEN)
flag_rect7="Cross"
turn_flag="Zero"
else:
canvas.point(pos,0x00ff00,width=50)
flag_rect7="Zero"
turn_flag="Cross"
check_winner()
elif RectWidth<pos[0]<2*RectWidth and 2*RectHeight<pos[1]<3*RectHeight:
if flag_rect8 not in ("Cross", "Zero"):
if turn_flag=="Cross":
draw_cross(pos, RGB_GREEN)
flag_rect8="Cross"
turn_flag="Zero"
else:
canvas.point(pos,0x00ff00,width=50)
flag_rect8="Zero"
turn_flag="Cross"
check_winner()
elif 2*RectWidth<pos[0]<3*RectWidth and 2*RectHeight<pos[1]<3*RectHeight:
if flag_rect9 not in ("Cross", "Zero"):
if turn_flag=="Cross":
draw_cross(pos, RGB_GREEN)
flag_rect9="Cross"
turn_flag="Zero"
else:
canvas.point(pos,0x00ff00,width=50)
flag_rect9="Zero"
turn_flag="Cross"
check_winner()
# Draw rectangles #1 #2 and #3
canvas.bind(key_codes.EButton1Down, down, ((0,0), (RectWidth,RectHeight)))
canvas.bind(key_codes.EButton1Down, down, ((RectWidth,0), (2*RectWidth,RectHeight)))
canvas.bind(key_codes.EButton1Down, down, ((2*RectWidth,0), (3*RectWidth,RectHeight)))
canvas.rectangle(((0,0), (RectWidth,RectHeight)), fill=board_colour, width=5,outline=RGB_BLACK)
canvas.rectangle(((RectWidth,0), (2*RectWidth,RectHeight)), fill=board_colour, width=5, outline=RGB_BLACK)
canvas.rectangle(((2*RectWidth,0), (3*RectWidth,RectHeight)), fill=board_colour, width=5,outline=RGB_BLACK)
# Draw rectangles #4 #5 and #6
canvas.bind(key_codes.EButton1Down, down, ((0,RectHeight), (RectWidth,2*RectHeight)))
canvas.bind(key_codes.EButton1Down, down, ((RectWidth,RectHeight), (2*RectWidth,2*RectHeight)))
canvas.bind(key_codes.EButton1Down, down, ((2*RectWidth,RectHeight), (3*RectWidth,2*RectHeight)))
canvas.rectangle(((0,RectHeight), (RectWidth,2*RectHeight)), fill=board_colour, width=5,outline=RGB_BLACK)
canvas.rectangle(((RectWidth,RectHeight), (2*RectWidth,2*RectHeight)), fill=board_colour, width=5,outline=RGB_BLACK)
canvas.rectangle(((2*RectWidth,RectHeight), (3*RectWidth,2*RectHeight)), fill=board_colour, width=5,outline=RGB_BLACK)
# Draw rectangles #7 #8 and #9
canvas.bind(key_codes.EButton1Down, down, ((0,2*RectHeight), (RectWidth,3*RectHeight)))
canvas.bind(key_codes.EButton1Down, down, ((RectWidth,2*RectHeight), (2*RectWidth,3*RectHeight)))
canvas.bind(key_codes.EButton1Down, down, ((2*RectWidth,2*RectHeight), (3*RectWidth,3*RectHeight)))
canvas.rectangle(((0,2*RectHeight), (RectWidth,3*RectHeight)), fill=board_colour, width=5,outline=RGB_BLACK)
canvas.rectangle(((RectWidth,2*RectHeight), (2*RectWidth,3*RectHeight)), fill=board_colour, width=5,outline=RGB_BLACK)
canvas.rectangle(((2*RectWidth,2*RectHeight), (3*RectWidth,3*RectHeight)), fill=board_colour, width=5,outline=RGB_BLACK)
app_lock = e32.Ao_lock()
app_lock.wait()
Let's go through this code in pieces. After importing the required modules, the color constants and flags used in the game are initialized.
# define colour constants
RGB_RED = (255, 0, 0)
RGB_GREEN = (0, 255, 0)
RGB_BLUE = (0, 0, 255)
RGB_BLACK=(0,0,0)
# define board colour
board_colour=RGB_BLUE
# define flag variables
turn_flag="Cross"
flag_rect1=None
flag_rect2=None
flag_rect3=None
flag_rect4=None
flag_rect5=None
flag_rect6=None
flag_rect7=None
flag_rect8=None
flag_rect9=None
In this example, we need to draw 2 shapes: a circle and cross. The circle is quite simple and can be achieved by a single line. Drawing a cross is a little more complex so we define a function to do this whenever needed: draw_cross(pos, cross_colour).
def draw_cross(pos, cross_colour):
'''Function to draw a cross - on the co-ordinates passed as arguments'''
cross_length=30
cross_width=15
a=pos[0]-cross_length
b=pos[1]-cross_length
while a<=pos[0]+cross_length:
canvas.point((a,b), width=cross_width, outline=cross_colour)
a+=1
b+=1
a=pos[0]+cross_length
b=pos[1]-cross_length
while a>=pos[0]-cross_length and b<=pos[1]+cross_length:
canvas.point((a,b), width=cross_width, outline=cross_colour)
a-=1
b+=1
down(pos) is the callback function which we bind to the down touch events. Here we check if the last shape drawn was a circle or a cross and accordingly draw the other one. A circle is drawn the first time the code is run (because was initialized as "Cross").
def down(pos):
''' Event handler '''
global turn_flag, flag_rect1,flag_rect2,flag_rect3,flag_rect4,flag_rect5,flag_rect6,flag_rect7,flag_rect8,flag_rect9
if 0<pos[0]<RectWidth and 0<pos[1]<RectHeight:
if flag_rect1 not in ("Cross", "Zero"):
if turn_flag=="Cross":
draw_cross(pos, RGB_GREEN)
flag_rect1="Cross"
turn_flag="Zero"
else:
canvas.point(pos,0x00ff00,width=50)
flag_rect1="Zero"
turn_flag="Cross"
check_winner()
elif RectWidth<pos[0]<2*RectWidth and 0<pos[1]<RectHeight:
if flag_rect2 not in ("Cross", "Zero"):
if turn_flag=="Cross":
draw_cross(pos, RGB_GREEN)
flag_rect2="Cross"
turn_flag="Zero"
else:
canvas.point(pos,0x00ff00,width=50)
flag_rect2="Zero"
turn_flag="Cross"
check_winner()
elif 2*RectWidth<pos[0]<3*RectWidth and 0<pos[1]<RectHeight:
if flag_rect3 not in ("Cross", "Zero"):
if turn_flag=="Cross":
draw_cross(pos, RGB_GREEN)
flag_rect3="Cross"
turn_flag="Zero"
else:
canvas.point(pos,0x00ff00,width=50)
flag_rect3="Zero"
turn_flag="Cross"
check_winner()
elif 0<pos[0]<RectWidth and RectHeight<pos[1]<2*RectHeight:
if flag_rect4 not in ("Cross", "Zero"):
if turn_flag=="Cross":
draw_cross(pos, RGB_GREEN)
flag_rect4="Cross"
turn_flag="Zero"
else:
canvas.point(pos,0x00ff00,width=50)
flag_rect4="Zero"
turn_flag="Cross"
check_winner()
print turn_flag, flag_rect4
elif RectWidth<pos[0]<2*RectWidth and RectHeight<pos[1]<2*RectHeight:
if flag_rect5 not in ("Cross", "Zero"):
if turn_flag=="Cross":
draw_cross(pos, RGB_GREEN)
flag_rect5="Cross"
turn_flag="Zero"
else:
canvas.point(pos,0x00ff00,width=50)
flag_rect5="Zero"
turn_flag="Cross"
check_winner()
elif 2*RectWidth<pos[0]<3*RectWidth and RectWidth<pos[1]<2*RectHeight:
if flag_rect6 not in ("Cross", "Zero"):
if turn_flag=="Cross":
draw_cross(pos, RGB_GREEN)
flag_rect6="Cross"
turn_flag="Zero"
else:
canvas.point(pos,0x00ff00,width=50)
flag_rect6="Zero"
turn_flag="Cross"
check_winner()
elif 0<pos[0]<RectWidth and 2*RectHeight<pos[1]<3*RectHeight:
if flag_rect7 not in ("Cross", "Zero"):
if turn_flag=="Cross":
draw_cross(pos, RGB_GREEN)
flag_rect7="Cross"
turn_flag="Zero"
else:
canvas.point(pos,0x00ff00,width=50)
flag_rect7="Zero"
turn_flag="Cross"
check_winner()
elif RectWidth<pos[0]<2*RectWidth and 2*RectHeight<pos[1]<3*RectHeight:
if flag_rect8 not in ("Cross", "Zero"):
if turn_flag=="Cross":
draw_cross(pos, RGB_GREEN)
flag_rect8="Cross"
turn_flag="Zero"
else:
canvas.point(pos,0x00ff00,width=50)
flag_rect8="Zero"
turn_flag="Cross"
check_winner()
elif 2*RectWidth<pos[0]<3*RectWidth and 2*RectHeight<pos[1]<3*RectHeight:
if flag_rect9 not in ("Cross", "Zero"):
if turn_flag=="Cross":
draw_cross(pos, RGB_GREEN)
flag_rect9="Cross"
turn_flag="Zero"
else:
canvas.point(pos,0x00ff00,width=50)
flag_rect9="Zero"
turn_flag="Cross"
check_winner()
After drawing each shape (Cross or Zero) we call the function. This checks if there are 3 correctly aligned zeros or crosses, and if so declares the winner using a note:
def check_winner():
'''Function to check the winner after each turn'''
global flag_rect1,flag_rect2,flag_rect3,flag_rect4,flag_rect5,flag_rect6,flag_rect7,flag_rect8,flag_rect9
if flag_rect1=="Cross" and flag_rect2=="Cross" and flag_rect3=="Cross"):
appuifw.note(u"Cross wins!")
elif flag_rect1=="Zero" and flag_rect2=="Zero" and flag_rect3=="Zero":
appuifw.note(u"Zero wins!")
elif flag_rect1=="Cross" and flag_rect4=="Cross" and flag_rect7=="Cross":
appuifw.note(u"Cross wins!")
elif flag_rect1=="Zero" and flag_rect4=="Zero" and flag_rect7=="Zero":
appuifw.note(u"Zero wins!")
elif flag_rect1=="Cross" and flag_rect5=="Cross" and flag_rect9=="Cross":
appuifw.note(u"Cross wins!")
elif flag_rect1=="Zero" and flag_rect5=="Zero" and flag_rect9=="Zero":
appuifw.note(u"Zero wins!")
elif flag_rect2=="Cross" and flag_rect5=="Cross" and flag_rect8=="Cross":
appuifw.note(u"Cross wins!")
elif flag_rect2=="Zero" and flag_rect5=="Zero" and flag_rect8=="Zero":
appuifw.note(u"Zero wins!")
elif flag_rect3=="Cross" and flag_rect6=="Cross" and flag_rect9=="Cross":
appuifw.note(u"Cross wins!")
elif flag_rect3=="Zero" and flag_rect6=="Zero" and flag_rect9=="Zero":
appuifw.note(u"Zero wins!")
elif flag_rect3=="Cross" and flag_rect5=="Cross" and flag_rect7=="Cross":
appuifw.note(u"Cross wins!")
elif flag_rect3=="Zero" and flag_rect5=="Zero" and flag_rect7=="Zero":
appuifw.note(u"Zero wins!")
elif flag_rect1=="Cross" and flag_rect2=="Cross" and flag_rect3=="Cross":
appuifw.note(u"Cross wins!")
elif flag_rect1=="Zero" and flag_rect2=="Zero" and flag_rect3=="Zero":
appuifw.note(u"Zero wins!")
elif flag_rect4=="Cross" and flag_rect5=="Cross" and flag_rect6=="Cross":
appuifw.note(u"Cross wins!")
elif flag_rect4=="Zero" and flag_rect5=="Zero" and flag_rect6=="Zero":
appuifw.note(u"Zero wins!")
elif flag_rect7=="Cross" and flag_rect8=="Cross" and flag_rect9=="Cross":
appuifw.note(u"Cross wins!")
elif flag_rect7=="Zero" and flag_rect8=="Zero" and flag_rect9=="Zero":
appuifw.note(u"Zero wins!")
Conclusion
This chapter has shown how you can add touch support to your applications, conditional on whether touch is supported by the device. Its shown how you can register the whole Canvas or just specific areas for touch events, and how you can thereby create your own custom touch-enabled UI elements. The text is accompanied by some instructive examples, including a touch piano, paint application and a tic-tac-toe game.
| Back to the Book |
Comments
Hamishwillee said…
Sign in to comment…


Hi Pankaj
Some great examples here!
Hope you like.
Regards H
Regards Hamish
--Hamishwillee 05:32, 10 March 2010 (UTC)