#!/usr/bin/env python """ -- Animating an object using graphics.py -- An example of * 'animating' a graphic (here it moves), and * 'waiting' for the user to click while that happens I've done this by replacing graphic.py's getMouse() with my own waitForClick(), which calls upkeep() every upkeep_interval seconds. The framework here is a single Application class that contains a GraphWin and a Rectangle from graphics.py. The control flow is 1. Application.run() repeatedly calls 2. Application.waitForClick(time_to_wait) which regularly calls 3. Application.upkeep() Note that this is *not* using threads. To make this more general purpose, you'd probably want to define a Widget class to replace self.rect here, one widget for each graphics_object in your Application window. Then you'd replace the main(), run(), and upkeep() methods below, doing something like looping over the Widgets to see which has been clicked and then doing the right thing - perhaps executing that widget's someoneClickedMe method. Jim Mahoney Dec 4 2006 """ from graphics import GraphWin, Rectangle, Point from time import time from random import randint class Application: """Demo animiation application""" colors = ('red', 'blue', 'green') color_index = 0 # current color of rectangle upkeep_interval = 0.2 # seconds between times self.upkeep() is called n_clicks = 10 # number of user clicks before quitting game time_per_click = 2.0 # max wait for user click def __init__(self): self.window = GraphWin('graphics window', 600, 600) # title, width, height self.rect = Rectangle(Point(10,10), Point(30, 30)) self.cycle_color() self.rect.draw(self.window) def wait_for_click(self, timeout=None): """Return mouse click Point or None if halted by timeout or stopWaiting.""" # based on GraphWin.getMouse from graphics.py. # This part self.window.mouseX = None self.window.mouseY = None self.waiting = True next_upkeep_time = time() + self.upkeep_interval if timeout: stop_time = time() + timeout while self.waiting and \ (self.window.mouseX == None or self.window.mouseY == None): self.window.update() now = time() if now > next_upkeep_time: next_upkeep_time += self.upkeep_interval self.upkeep() if timeout and now > stop_time: self.waiting = False if self.window.mouseX: x,y = self.window.toWorld(self.window.mouseX, self.window.mouseY) return Point(x,y) else: return None def cycle_color(self): """Change the color of the rectangle.""" self.rect.setFill(self.colors[self.color_index]) self.color_index = (self.color_index + 1) % len(self.colors) def upkeep(self): """Perform any needed periodic tasks while waiting for a click.""" # To stop waiting for a click, do 'self.waiting = False' dx, dy = randint(1,8), randint(1,8) self.rect.move(dx, dy) def point_in_rect(self, point): """Return True if point is in self.rect""" graph_obj = self.rect x, y = point.getX(), point.getY() x1, y1, x2, y2 = graph_obj.canvas.bbox(graph_obj.id) # bounding box x1, y1 = self.window.toWorld(x1, y1) # convert coords x2, y2 = self.window.toWorld(x2, y2) # print " debug: point = ", x, y # print " rect bounds x1,x2,y1,y2 =", x1, x2, y1,y 2 return x1 <= x <= x2 and y1 <= y <= y2 def run(self): """Run the animation and handle user clicks.""" for i in range(self.n_clicks): click = self.wait_for_click(self.time_per_click) if click: print "mouse click", (i+1), "at (%s, %s)" % (click.getX(),click.getY()) if self.point_in_rect(click): print "click is inside box" self.cycle_color() else: print "YOU MISSED" else: print "OOPS - time for click", (i+1),"expired." self.cycle_color() def main(self): print "-- Animation demo --" print "You can click", self.n_clicks, "times in the window." print "Clicking inside the box changes its color, but" print "you only have", self.time_per_click, "seconds per click" print "before it changes color on its own." print "Click anywhere in the graphics window to start things off." # input = raw_input("Hit return to start.") click = self.window.getMouse() print "Go." self.run() print "Done." input = raw_input("Hit return to quit.") Application().main() """ Jim's notes : The graphics.py module used Tk to do the real work, and in particular a 'canvas' that can things drawn within it. The Rectangles etc that graphics.py creates are are 'items' with integer id's within the canvas. Here are some methods defined within Tk to deal with these things. canvas.after (ms, func=None, *args) Requests Tkinter to call function func with arguments args after a delay of at least ms milliseconds. There is no upper limit to how long it will actually take, but your callback won't be called sooner than you request, and it will be called only once. This method returns an integer 'after identifier' that can be passed to the .after_cancel() method if you want to cancel the callback. canvas.after_cancel (id) Cancels a request for callback set up earlier .after(). The id argument is the result returned by the original .after() call. (x1, y1, x2, y2) = canvas.bbox(id) # bounding box canvas.move(id, dx, dy) canvas.tag_bind(id, sequence=event, function=callback) """