|
Programmer's Notebook |
All computer source code presented on this page, unless it includes attribution to another author, is provided by Ed Halley under the Artistic License. Use such code freely and without any expectation of support. I would like to know if you make anything cool with the code, or need questions answered.
python/ bindings.py boards.py buzz.py cache.py cards.py constraints.py csql.py english.py getopts.py gizmos.py goals.py improv.py interpolations.py namespaces.py nihongo.py nodes.py octalplus.py patterns.py persist.py physics.py pids.py pieces.py quizzes.py recipes.py relays.py romaji.py ropen.py sheets.py strokes.py subscriptions.py svgbuild.py testing.py things.py timing.py ucsv.py useful.py uuid.py vectors.py weighted.py java/ CSVReader.java CSVWriter.java GlobFilenameFilter.java RegexFilenameFilter.java StringBufferOutputStream.java ThreadSet.java Throttle.java TracingThread.java Utf8ConsoleTest.java droid/ ArrangeViewsTouchListener.java DownloadFileTask.java perl/ CVQM.pm Kana.pm Typo.pm cxx/ CCache.h equalish.cpp |
Download things.py
|
# things - a generic "small simulation" application framework for pygame ''' A generic "small simulation" application framework for pygame. ABSTRACT The "things" library is a very simple generic application framework, organizing interactive objects for rendering. Partners with the pygame library for the actual rendering and device interaction capabilities. The simulation is built on classes that extend a pair of basic root classes, Item and Thing. Items can represent anything, while Things represent visible, tangible entities that are positioned or move around in the simulation. AUTHOR Ed Halley (ed@halley.cc) 6 November 2007 ''' import os import glob import math import random import pygame ; from pygame.locals import * import vectors ; from vectors import * import goals ; from goals import * try: pass #import psyco ; psyco.full() except: pass class Context (object): pass #---------------------------------------------------------------------------- class Item (object): '''The Item is the simplest abstract root class in the library. Items have style flags, goals, and define the basic four components of a simulation loop: event(), think(), visit(), and paint(). * Style flags: every item has a set of keywords as style flags; the actual keywords are mostly application defined, though this library uses keywords like "pickable" to enable or disable built-in interaction behaviors. * Goals: each item has its own independent "bunch-queue" of future goals that are being worked. Goals can be "added" to let them run in parallel, or they can be "appended" to wait for earlier subgoals to complete fully before starting. * Simulation loop: + event(): called as required to respond to pygame or application events as the simulation runs + think(): may be called once per simulation loop to allow items to make progress on their goals or other tasks + visit(): may be called once per simulation loop to collect or cull any rendering state information required + paint(): may be called once per rendering pass to perform the actual drawing work required, such as to pygame or opengl surfaces ''' def __init__(self): self.style = set([]) self.goals = [ ] def is_style(self, style): return style in self.style def set_style(self, style, value=True): if not value: if style in self.style: self.style.remove(style) else: self.style.add(style) def add_goal(self, goal): '''Adds a new goal to the goal queue, which can progress in parallel with other added goals. ''' if not self.goals: return self.append_goal(goal) self.goals[-1].add(goal) return self def append_goal(self, goal): '''Appends a mark, followed by new goal to the goal queue, ensuring all prior goals have been completed before this goal begins. ''' self.goals.append( ParallelGoal(goal) ) return self def wait(self, when=None, dt=0., todo=None): '''Appends a mark to the goal queue, to ensure all prior goals have been completed before later goals begin. Optionally specify an absolute time to finish with a "when" value. Optionally specify a relative time to finish with a "dt" value. (If both are given, when+dt is the finish target.) ''' if when is None and dt == 0.: dt = 0.00001 self.goals.append( TimeGoal(when=when, dt=dt, todo=todo) ) self.goals.append( ParallelGoal( ) ) return self def stop(self): '''Removes all goals, complete or not, from the goal queue.''' self.goals = [ ] return self def is_idle(self): '''Returns True if the item has no active goals at all.''' return not self.goals def event(self, event): '''Receive a pygame or application event for processing. Any override of this method should also call this version. ''' pass def think(self, context): '''Check the active goals on the goal queue for progress. Any override of this method should also call this version. ''' while self.goals: if not self.goals[0].done(): break self.goals.pop(0) def visit(self, context): '''Decide and digest any information required for processing or rendering in the other callbacks for each loop. Any override of this method should also call this version. ''' pass def paint(self, context): '''Perform any rendering required for this item. Any override of this method should also call this version. ''' #REVIEW: remove this method? pass #---------------------------------------------------------------------------- class Thing (Item): '''A Thing is an Item with a location. This class extends the basic Item class, providing position information, a metaphysics model that lets one thing "follow" another, and capabilities of hit-testing. * Positioning: every thing has a "place" and an "angle" which represent their position and orientation information within a given frame of reference; methods move() and turn() offer an easy way to add motion-oriented goals so things can travel with minimal programming effort * Following: the position of a thing is relative to a reference frame, which can be assigned to almost any other thing; this allows things to appear connected in parent/child relationships so only the parent motion must be specified * Hit-testing: mouse events are routed to each visible thing, taking their distance from the viewer into account; each thing can then decide if the pointer is actually intersecting with the thing, or if the thing is not even interactive ''' def __init__(self): super(Thing, self).__init__() self.frame = None self.joins = set([]) self.place = V(0,0,0) self.angle = V(0,0,0) # euler angles self.xform = M() # overwritten on each visit def hits(self, context, hither, yon): '''Called to see if the ray between hither and yon hits this thing. By default, we return None. Override with hit testing logic, and return self if hit. ''' return None def followers(self): '''Returns a list of our direct followers. These are the things which directly use us as a frame of reference. ''' return list(self.joins) def follows(self, frame): '''Checks if we follow (directly or indirectly) a given frame.''' if self.frame == frame: return True if self.frame is None: return False return self.frame.follows(frame) def follow(self, frame): '''Assigns another thing as the frame of reference for this thing. Cyclical relationships (following oneself) are not allowed. ''' if not isinstance(frame, Thing): raise TypeError, 'reference frame must be another Thing' if frame.follows(self): raise TypeError, 'cannot make circular reference frame' if self.frame is frame: return if self.frame: self.unfollow() self.place -= frame.xform.translation() self.angle -= frame.xform.rotation() self.frame = frame frame.admit(self) return self def unfollow(self): '''Disconnect the following frame of reference. Note that after disconnection, this thing will no longer get any calls to visit() or paint(). ''' self.frame.eject(self) self.place += self.frame.xform.translation() self.angle += self.frame.xform.rotation() self.frame = None return self def is_leader(self): '''True if there are any followers.''' return not self.joins def admit(self, follower): '''Connects the given follower to our own set of known followers. Usually not called directly; tell the follower to follow() instead. ''' self.joins.add(follower) def eject(self, follower): '''Disconnects the given follower to our own set of known followers. Usually not called directly; tell the follower to unfollow() instead. ''' if follower in self.joins: self.joins.remove(follower) def space(self): '''Returns the ultimate frame of reference, that which we follow but does not follow anything. Usually a Space.''' if not self.frame: return self return self.frame.space() def visit(self, context): '''Called on each simulation loop to determine global transformations for each thing. If you wish to override this method to collect other context information, call this base version also. ''' context.push() context.journal(self) self.xform = context.get() for thing in self.joins: thing.visit(context) context.pop() def think(self, context): '''Allows for independent object decision-making behavior. The base method currently does no thinking in particular, but any override should call this base version also. ''' super(Thing, self).think(context) joins = self.joins.copy() for thing in joins: thing.think(context) def paint(self, context): '''Allows for painting to the screen surface. An override can use the context information to facilitate this. The base method currently does no painting in particular, but any override should call this base version also. ''' pass def move(self, places, relative=False, when=None, dt=0.0): '''Apply a new place coordinate, interpolated on a curve over time. If one vector is given, trace linearly from current to new place. If a list of vectors is given, trace a bezier path to the new place. ''' if not self.frame: raise RuntimeError, 'cannot move thing with no reference frame' self.add_goal(ChangeGoal(self.place, places, relative=relative, when=when, dt=dt)) return self def turn(self, angles, relative=False, when=None, dt=0.0): '''Apply a new orientation angle, interpolated on a curve over time. If one vector is given, trace linearly from current to new place. If a list of vectors is given, trace a bezier path to the new place. ''' if not self.frame: raise RuntimeError, 'cannot turn thing with no reference frame' self.add_goal(ChangeGoal(self.angle, angles, relative=relative, when=when, dt=dt)) return self def ping(self, thing, event=None, when=None, dt=0.0): '''Add a goal to send an event or trigger to another item.''' if not isinstance(event, pygame.Event): event = pygame.event.Event(USEREVENT, code=event) todo = lambda x,them=thing,that=event: them.event(that) self.add_goal(TimeGoal(when=when, dt=dt, todo=todo)) return self #---------------------------------------------------------------------------- class Space (Thing): def __init__(self, context=None): '''An ultimate container for a set of things.''' super(Space, self).__init__() if not context: context = MatrixContext() self.context = context self.picked = set([]) self.moused = None self.camera = V.Z * 1000 self.hyper = None def segue(self): '''A callback checked on every event loop. Returning a Space-based type, or a Space instance, switches spaces. Default version always returns self, indicating we should continue. A return of None would indicate a request to shut down the app, or let the app pick some new space instance to create instead. ''' return self def modal(self): return False def follow(self, frame): '''A thing can follow a space, but a space cannot follow anything. Throws an error if this is attempted. ''' raise TypeError, 'a Space cannot follow any other thing or space' def background(self, surface): '''A simple base implementation used for painting the background.''' surface.fill( (0x00, 0x80, 0x00) ) def visit(self, surface): '''Does the thinking for the space itself, and asks all of the things in the space to think for themselves in arbitrary order. ''' self.context.reset() self.context.media = surface super(Space, self).visit(self.context) if self.hyper: self.hyper.visit(surface) def think(self): '''Does the thinking for the space itself, and asks all of the things in the space to think for themselves in arbitrary order. ''' super(Space, self).think(self.context) for thing in self.joins: thing.think(self.context) if self.hyper: self.hyper.think() def paint(self, surface): '''Does the painting for the space itself, and asks all of the things in the space to paint themselves in the proper ordering. ''' self.context.media = surface visited = self.context.visited(self.camera) for entry in visited: (thing, transform, where, z) = entry if self is thing: self.background(surface) else: self.context.load(transform) thing.paint(self.context) if self.hyper: self.hyper.paint(surface) def hits(self, context, hither, yon): '''Asks the various things in the space to perform their hit-testing in the proper ordering as required. ''' visited = context.visited(self.camera) for entry in reversed(visited): (thing, transform, where, z) = entry if self is thing: return self else: hit = thing.hits(context, hither, yon) if hit: return hit return None def event(self, event): '''Routes any event that happens in this space to the right things. ''' if self.hyper: if self.hyper.segue() is None: self.hyper = None else: self.hyper.event(event) if self.hyper.modal(): return if event.type in (KEYDOWN, KEYUP): for thing in self.picked: thing.event(event) if event.type == MOUSEBUTTONDOWN: hither = V(event.pos[0], event.pos[1], 1000) yon = V(event.pos[0], event.pos[1], 0) thing = self.hits(self.context, hither, yon) if not thing: self.unpick() else: if thing is not self: thing.event(event) self.moused = thing if thing.is_style('pickable'): if thing in self.picked: self.unpick(thing) else: self.pick(thing) if event.type == MOUSEMOTION: if self.moused: self.moused.event(event) if event.type == MOUSEBUTTONUP: if self.moused: self.moused.event(event) self.moused = None def pick(self, thing): '''Adds a thing to the "picked" group.''' self.picked.add(thing) thing.set_style('picked') def unpick(self, thing=None): '''Removes a thing (or all things) from the "picked" group.''' if thing is None: for thing in self.picked.copy(): self.unpick(thing) return if thing in self.picked: self.picked.remove(thing) thing.set_style('picked', False) #---------------------------------------------------------------------------- class Twister (object): def __init__(self, steps=45): self.twist = { } self.steps = int(steps) def twist_image(original): '''Takes an image and returns a rotated version.''' if original in self.twist: return spun = { } for angle in range(0, 360, self.steps): center = original.get_rect().center image = pygame.transform.rotate(original, angle) rect = image.get_rect(center=center) spun[angle] = (image, rect) self.twist[original] = spun def get_twisted_image(original, angle): '''Returns the best twisted image for an arbitrary given angle.''' if not original in self.twist: self.twist_image(original) angle = (int(((angle+self.steps/2.)/self.steps))*self.steps) % 360 return self.twist[original][angle] #---------------------------------------------------------------------------- class Context (object): '''This abstract internal class is for collecting a view onto state information for rendering or other purposes. A specific kind of context is created and used by the application to visit all known items, and prepare for painting, physics, and event-processing. ''' def __init__(self, media=None): '''Sets up a new context for simulation logic. Optionally provide a pygame surface as the painting media. ''' self.media = media self.reset() def reset(self): '''Prepare the context for another simulation loop. The transformation stack reverts to the identity matrix. The journal of visit() call results is cleared. ''' self.visit = [ ] self.specs = { } def get(self): '''Retrieves the current transform state.''' return None def push(self): '''Saves this transform state on an internal stack.''' pass def pop(self): '''Returns to prior pushed transform state, or the identity state.''' pass def do(self, transform): '''Accumulates a change to the current transform state.''' pass def load(self, transform): '''Replaces the current transform state with a specific matrix.''' pass def point(self, p): '''Transforms a local-space point to its global-space location.''' pass def journal(self, thing): '''Keep track of the status of a thing, such as its world position.''' entry = [ thing ] self.visit.append( entry ) self.specs[thing] = entry def visited(self, hither=None): '''Return a list of things, optionally sorted by a view distance.''' return self.visit def explain(self, thing): '''Retrieve the context information about a given thing.''' return self.specs.get(thing, None) #---------------------------------------------------------------------------- class VectorContext (Context): '''This context supports only a 3D vector transform stack for every thing. The things are controlled only by their place member; the angle members are completely ignored. While this means that things have no rotation capabilities, and only an offset relative to their frame of reference, the computational power is much more streamlined for many objects on even low-powered devices. ''' def __init__(self, media=None): '''Sets up a new context for simulation logic. Optionally provide a pygame surface as the painting media. ''' super(VectorContext, self).__init__(media) self.hither = None def reset(self): '''Prepare the context for another simulation loop. The transformation stack reverts to the identity matrix. The journal of visit() call results is cleared. ''' self.stack = [ V(0,0,0) ] super(VectorContext, self).reset() def get(self): '''Retrieves the current transform state.''' return M.translate(self.stack[-1]) def push(self): '''Saves this transform state on an internal stack.''' self.stack.append(V(self.stack[-1])) def pop(self): '''Returns to prior pushed transform state, or the identity state.''' self.stack.pop() if not self.stack: self.stack = [ V() ] def do(self, transform): '''Accumulates a change to the current transform state.''' if isinstance(transform, M): transform = transform.translation() self.stack[-1] += transform def load(self, transform): '''Replaces the current transform state with a specific matrix.''' if isinstance(transform, M): transform = transform.translation() self.stack[-1] = transform def point(self, p): '''Transforms a local-space point to its global-space location.''' return p + self.stack[-1] def journal(self, thing): '''Keep track of the status of a thing, such as its world position.''' if thing.place: self.stack[-1] += thing.place transform = V(self.stack[-1]) where = transform entry = [ thing, transform, where, where[2] ] # redundant transform/where above, but matches MatrixContext self.visit.append( entry ) self.specs[thing] = entry def visited(self, hither=None): '''Return a list of things, optionally sorted by a view distance.''' self.visit.sort(cmp=lambda a,b: cmp(a[3],b[3])) if hither is not self.hither: self.hither = hither return self.visit #---------------------------------------------------------------------------- class MatrixContext (Context): '''This context supports a full 4x4 matrix transform stack for every thing. The things are controlled by their place and angle members. This means that each thing has a complete six degrees of freedom relative to their frame of reference. Can be computationally slow on some low-powered devices. ''' def __init__(self, media=None): '''Sets up a new context for simulation logic. Optionally provide a pygame surface as the painting media. ''' super(MatrixContext, self).__init__(media) self.hither = None def reset(self): '''Prepare the context for another simulation loop. The transformation stack reverts to the identity matrix. The journal of visit() call results is cleared. ''' self.stack = [ M() ] super(MatrixContext, self).reset() def get(self): '''Retrieves the current transform state.''' return M(self.stack[-1]) def push(self): '''Saves this transform state on an internal stack.''' self.stack.append(M(self.stack[-1])) def pop(self): '''Returns to prior pushed transform state, or the identity state.''' self.stack.pop() if not self.stack: self.stack = [ M() ] def do(self, transform): '''Accumulates a change to the current transform state.''' self.stack[-1] *= transform def load(self, transform): '''Replaces the current transform state with a specific matrix.''' self.stack[-1] = transform def point(self, p): '''Transforms a local-space point to its global-space location.''' return p * self.stack[-1] def journal(self, thing): '''Keep track of the status of a thing, such as its world position.''' if thing.place: self.do(M.translate(thing.place)) if thing.angle[2] != 0.0: self.do(M.rotate('Z', thing.angle[2])) transform = M(self.stack[-1]) where = transform.translation() # V.O * transform entry = [ thing, transform, where, where[2] ] self.visit.append( entry ) self.specs[thing] = entry def visited(self, hither=None): '''Return a list of things, optionally sorted by a view distance.''' self.visit.sort(cmp=lambda a,b: cmp(a[3],b[3])) if hither is not self.hither: self.hither = hither return self.visit #---------------------------------------------------------------------------- class App (Item): __resources = { } def __init__(self, name, size=(800,480)): '''A basic application loop ready to employ Item and Thing classes using pygame features to draw and manage interaction. ''' super(App, self).__init__() self.fps = 30 self.quit = False self.keys = { K_LCTRL: False, K_RCTRL: False, K_LSHIFT: False, K_RSHIFT: False, K_LMETA: False, K_RMETA: False, K_LALT: False, K_RALT: False } pygame.init() self.resize( size ) pygame.display.set_caption(name) self.space = None self.graph = { } self.clock = pygame.time.Clock() def reshape(self): #TODO: if self.mode & FULLSCREEN: self.mode &= ~FULLSCREEN screen = pygame.display.set_mode(self.size, self.mode) else: self.mode |= FULLSCREEN screen = pygame.display.set_mode(self.size, self.mode) self.screen = screen #self.context = VectorContext(screen) def resize(self, size=None): '''Sets up the basic window size. Also called if the user resizes.''' self.mode = HWSURFACE # | RESIZABLE self.size = size self.screen = pygame.display.set_mode(self.size, self.mode) #self.context = VectorContext(screen) @classmethod def image(cls, size, flags=0): if (flags & SRCALPHA): image = pygame.Surface(size, flags, 32).convert_alpha() image.fill( (0,0,0,0) ) else: image = pygame.Surface( size, flags ) return image @classmethod def find_resource(cls, name): for path in [ '.', 'res', 'resource', 'resources' ]: attempt = os.path.join(path, name) if os.path.exists(attempt): return attempt return None @classmethod def resource(cls, name, *args): if name in App.__resources: return App.__resources[name] resource = None found = cls.find_resource(name) if not found: raise ValueError, 'could not find resource %s' % name (file, ext) = os.path.splitext(found) if ext == '.ttf': name = name + repr(*args) if name in App.__resources: return App.__resources[name] resource = pygame.font.Font(found, *args) elif ext in [ '.jpg', '.png', '.gif' ]: image = pygame.image.load(found) convert = True if len(args): convert = args.pop(0) if convert: if ext == '.jpg': image = image.convert() else: image = image.convert_alpha() resource = image elif ext in [ '.txt' ]: f = open(found) if not f: raise ValueError, 'could not open %s for reading' % name resource = f.read() f.close() if not resource: raise ValueError, 'unknown resource type %s' % ext App.__resources[name] = resource return resource @classmethod def resources(cls, wildcard, *args): found = { } for path in [ '.', 'res', 'resource', 'resources' ]: attempt = os.path.join(path, wildcard) files = glob.glob(attempt) for file in files: name = os.path.basename(file) found[name] = cls.resource(file, *args) if not found: raise ValueError, 'could not find any resources %s' % wildcard return found def think(self): '''One space may be active at a time. The active space can think.''' if self.space: self.space.think() def visit(self): '''The active space is visited to collect rendering state data.''' #self.context.reset() if self.space: self.space.visit(self.screen) def paint(self): '''The active space is painted into the context.''' if self.space: self.space.paint(self.screen) def event_standard(self, event): '''Handles common application events such as requests to quit.''' if event.type == QUIT: self.quit = True if event.type == VIDEORESIZE: self.resize(event.size) def event_common(self, event): '''Handles common application events such as tracking shift-keys.''' if event.type == KEYDOWN: if event.key in self.keys: self.keys[event.key] = True if event.type == KEYUP: if event.key in self.keys: self.keys[event.key] = False def event_vista(self, event): '''Handles common Windows-platform events.''' if event.type == KEYDOWN: if event.key == K_F4: self.quit = True if event.key == K_KP_ENTER or event.key == K_RETURN: if self.keys[K_LMETA] or self.keys[K_RMETA]: pass # self.reshape() def event_apple(self, event): '''Handles common Apple-platform events.''' # Command keys are K_LMETA and K_RMETA # KEYDOWN K_*META + Q for quit if event.type == KEYDOWN: if event.key == K_q: if self.keys[K_LMETA] or self.keys[K_RMETA]: self.quit = True def event_maemo(self, event): '''Handles common Maemo-platform (e.g., Nokia N810) events.''' # KEYDOWN K_F6 is FULLSCREEN/window toggle # KEYDOWN K_F7 is zoomin/biggertext # KEYDOWN K_F8 is zoomout/smallertext # KEYDOWN K_F5 is appswitch # KEYDOWN K_F4 is menu # KEYDOWN K_RETURN is center of dpad # KEYDOWN K_ALTGR is Fn (a sticky mod) # KEYDOWN K_COMPOSE is Chr (a sticky mod) # (note that both shift keys give K_LSHIFT on N810) if event.type == KEYDOWN: if event.key == K_F6: pass # self.reshape() def route(self): '''Standard event routing and processing loop.''' for event in pygame.event.get(): self.event_standard(event) self.event_common(event) #self.event_vista(event) self.event_apple(event) self.event_maemo(event) self.event(event) def event(self, event): if self.space: self.space.event(event) def segue(self): '''Watch for any transition from one space to another.''' if not self.space: return segue = self.space.segue() if segue is None: name = self.space.__class__.__name__ if name in self.graph: segue = self.graph[name] if segue is self.space: return if segue is None: self.quit = True return if isinstance(segue, (list,tuple)): segue = random.choice(segue) if isinstance(segue, str): if segue in globals(): segue = globals()[segue] if callable(segue): segue = segue() if isinstance(segue, Space): self.space = segue def thaw(self): '''This method is called before the main loop starts.''' pass def freeze(self): '''This method is called after the main loop quits.''' pass def run(self): '''The basic event loop of the application. Opens the window and continues until the user quits. May only be called once. ''' self.thaw() while not self.quit: self.segue() self.route() self.think() self.visit() self.paint() pygame.display.flip() self.clock.tick(self.fps) self.freeze() pygame.quit() #---------------------------------------------------------------------------- import interpolations class _TestPad (Thing): def __init__(self, size=None, color=None): super(_TestPad, self).__init__() if not size: size = (200, 200) self.size = size self.nib = App.resource('spot-12x12.png') fat = self.nib.get_rect() self.fat = (fat.width/2,fat.height/2) self.pad = App.image(self.size, SRCALPHA) self.pad.fill( (0,0,0,0) ) if color: self.pad.fill( color ) self.stylus = V(0, 0) self.pressure = V(0.) def draw(self, where, pressure): d = distance(self.stylus, where) for t in range(0, int(d), self.fat[0]): stylus = interpolations.linear(0, d, t, self.stylus, where) self.pad.blit(self.nib, (stylus[0]-self.fat[0], stylus[1]-self.fat[1])) self.stylus = where def hits(self, context, hither, yon): h = super(_TestPad, self).hits(context, hither, yon) if h: return h if hither[0] < self.place[0]-self.size[0]/2: return None if hither[1] < self.place[1]-self.size[1]/2: return None if hither[0] >= self.place[0]+self.size[0]/2: return None if hither[1] >= self.place[1]+self.size[1]/2: return None return self def event(self, event): super(_TestPad, self).event(event) if event.type == MOUSEBUTTONDOWN: where = V(event.pos[0]-self.place[0]+self.size[0]/2, event.pos[1]-self.place[1]+self.size[1]/2) self.stylus = where self.pressure = V(1.) self.draw(where, 1.) if event.type == MOUSEMOTION: where = V(event.pos[0]-self.place[0]+self.size[0]/2, event.pos[1]-self.place[1]+self.size[1]/2) pressure = self.pressure[0] if pressure > 0.: self.draw(where, pressure) if event.type == MOUSEBUTTONUP: where = V(event.pos[0]-self.place[0]+self.size[0]/2, event.pos[1]-self.place[1]+self.size[1]/2) pressure = self.pressure[0] if pressure > 0.: self.draw(where, pressure) self.pressure = V(0.) def paint(self, context): context.media.blit(self.pad, (self.place[0] - self.size[0]/2, self.place[1] - self.size[1]/2)) class _TestSpace (Space): def __init__(self, *args): super(_TestSpace, self).__init__(*args) self.text = [ "Press keys to see their event codes.", "Press Esc to quit." ] self.font = App.resource("FreeSans.ttf", 25) self.pad = _TestPad( (300,200), (0xFF,0xFF,0xFe) ) self.pad.follow(self) self.pad.move( V(400,250,0) ) self.quit = False def segue(self): if self.quit: return None return super(_TestSpace, self).segue() def event(self, event): if event.type == KEYDOWN: text = "KEYDOWN %s=%s" % ( str(event.key), pygame.key.name(event.key) ) self.text.append(text) if event.key == K_ESCAPE: self.quit = True while len(self.text) > 10: self.text.pop(0) super(_TestSpace, self).event(event) def paint(self, surface): super(_TestSpace, self).paint(surface) y = 25 for line in self.text: text = self.font.render( line, 1, (0xFF,0xCC,0x66) ) self.context.media.blit( text, (15, y) ) y += text.get_rect().height if __name__ == '__main__': app = App('Demo - press Esc to quit') app.space = _TestSpace() app.run() |
|
Contact Ed Halley by email at
ed@halley.cc. Text, code, layout and artwork are Copyright © 1996-2013 Ed Halley. Copying in whole or in part, with author attribution, is expressly allowed. Any references to trademarks are illustrative and are controlled by their respective owners. |
|