#! /usr/bin/env python # # Various Menu interaces for use with Pygame # Copyright (C) 2001 Michael Urman # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # """ gamemenu: baseclass and implementation for various game "menus." Menu will allow you to add a graphical context menu to your games with relative ease. Examples of support types include: - Rotating Icons (like Secret of Mana) in class RotatingIconMenu - Circling Text (like The Sims) in class CircleTextMenu Running this program instead of using it as a library will let you examine several examples. If you want more menu styles, implement a derived class (or tree) from GameMenu or RadialMenu, and see the RotatingIconMenu and CircleTextMenu for example code. If you like the style of menu but want to change how icons/text/desc are rendered, derive a class from the Icon, Text, or Desc with your own render_str(). Then to use these, instead of menu.add_item(..., icon='file.png', ...) use menu.add_item(..., icon=MyIcon('file.png'), ...) and similarly for text= and desc= Example render_str() functions are available in Icon, Text, and Desc classes. """ __version__ = '0.2' __date__ = '2002/06/05' __author__ = 'Michael Urman (mu on irc.openprojects.net)' # watch me go out of my way to avoid needing the pygame.locals.* so as not to # clutter the pydoc info :) import pygame import copy try: import Numeric as _N except ImportError: _N = None class MenuAttribute: """MenuAttribute is the base of drawn menu attributes""" def __init__(self, *args): """MenuAttribute(*args) -> MenuAttribute Instantiates a MenuAttribute (or derived class) according to arguments. Currently the only accepted format is: - MenuAttribute(string) (meaning depends on the subclass)""" if len(args) == 1 and isinstance(args[0], type('')): self.render_str(args[0]) self.dim = self.images[0].get_width(), self.images[0].get_height() elif len(args) == 1 and args[0] is None: self.dim = None self.images = None else: print "args", args raise ValueError("MenuAttribute initialization requires a string") def __getattr__(self, attr): """Quick access functionality to width, height, size, and rect""" if attr in ('width', 'height', 'size', 'rect'): return getattr(self, 'get_'+attr)() else: raise AttributeError("MenuAttribute instance has no attribute '%s'"%attr) def get_size(self): """get_size(self) -> width, height Return tuple of width and height of image""" return self.dim[:] def get_width(self): """get_width(self) -> width Return the width of image""" return self.dim[0] def get_height(self): """get_height(self) -> height Return the height of image""" return self.dim[1] def get_rect(self): """get_rect(self) -> rect Return the image's rect""" return self.images[0].get_rect() def get_image(self, image=0): """get_image(self, image=0) -> surf Return the image indexed by argument image""" return self.images[image] def render_str(self, str): raise NotImplementedError("render_str() was not implemented in the derived class") def desaturate(self, surf): """desaturate(self, surf) -> surf Creates a new surface and fills it with the greyscale according to the Gimp's Y = 0.3R + 0.59G + 0.11B""" if 0 and pygame.surfarray: rgbimg = pygame.surfarray.array3d(surf) rgbarray = _N.array(rgbimg) rgbarray[:] *= (0.3, 0.59, 0.11) return pygame.surfarray.make_surface(rgbarray) w, h = surf.get_size() desat = pygame.Surface((w,h), surf.get_flags(), surf) if desat.get_flags() & pygame.SRCALPHA: for y in range(h): for x in range(w): r,g,b,a = surf.get_at((x,y)) lum = int(0.3*r + 0.59*g + 0.11*b) desat.set_at((x,y), (lum,lum,lum, a)) else: for y in range(h): for x in range(w): r,g,b = surf.get_at((x,y)) lum = int(0.3*r + 0.59*g + 0.11*b) desat.set_at((x,y), (lum,lum,lum)) return desat def hilight(self, surf, color=(255,255,255), percent=0.33): """hilight(self, surf) -> surf Creates a new surface and fills it with a highlighted version by blitting a 33% white on top.""" if 0 and pygame.surfarray: rgbimg = pygame.surfarray.array3d(surf) rgbarray = _N.array(rgbimg) diff = _N.zeros(rgbarray.shape) diff[:] = color diff = (diff - rgbarray) * percent return pygame.surfarray.make_surface(rgbarray + diff.astype(_N.Int)) w, h = surf.get_size() srcp = 1-percent; hr, hb, hg = [chan * percent for chan in color] hilight = pygame.Surface((w,h), surf.get_flags(), surf) if hilight.get_flags() & pygame.SRCALPHA: for y in range(h): for x in range(w): r,g,b,a = surf.get_at((x,y)) r = srcp*r + hr g = srcp*g + hb b = srcp*b + hg hilight.set_at((x,y), (r,g,b,a)) else: for y in range(h): for x in range(w): r,g,b = surf.get_at((x,y)) r = srcp*r + hr g = srcp*g + hg b = srcp*b + hb hilight.set_at((x,y), (r,g,b)) return hilight class Icon(MenuAttribute): """Icon abstracts the icons used in the RotatingIconMenu""" rendered = {} def render_str(self, str): """render_str(self, str) -> None Render a set of images for the icon, treating str as an icon file""" self.iconfile = str if Icon.rendered.has_key(str): self.images = Icon.rendered[str] else: image = pygame.image.load(str) image_sel = self.hilight(image, (255,255,100), 0.4) image_disable = self.desaturate(image) self.images = (image, image_sel, image_disable) Icon.rendered[str] = self.images class Desc(MenuAttribute): """Desc abstracts the description information""" FONT = None SIZE = 24 COLOR = (255,255,255) rendered = {} def render_str(self, str): """render_str(self, str) -> None Render a set of images for the description, treating str as a title""" self.desc = str if Desc.rendered.has_key(str): self.images = Desc.rendered[str] else: font = pygame.font.Font(Desc.FONT, Desc.SIZE) image = font.render(str, 1, Desc.COLOR) #image_sel = self.hilight(image) #image_disable = self.desaturate(image) #self.images = (image, image_sel, image_disable) self.images = (image, image, image) Desc.rendered[str] = self.images class Text(MenuAttribute): """Text abstracts the text used in the CircleTextMenu""" FONT = None SIZE = 18 COLOR = (255,255,255) BGCOLOR = (40,40,120) rendered = {} def render_str(self, str): """render_str(self, str) -> None Render a set of images for the text, treating str as a label""" self.text = str if Text.rendered.has_key(str): self.images = Text.rendered[str] else: font = pygame.font.Font(Text.FONT, Text.SIZE) img = font.render(str, 1, Text.COLOR) w, h = img.get_size() # BUG?: following doesn't work without ", 32" textbg = pygame.Surface((w+h, h), pygame.SRCALPHA, 32) r = h/2.0 pygame.draw.circle(textbg, Text.BGCOLOR, (r,r), r, 0) pygame.draw.circle(textbg, Text.BGCOLOR, (w+r,r), r, 0) pygame.draw.rect(textbg, Text.BGCOLOR, (r, 0, w, h), 0) textbg.blit(img, (r,0)) text_sel = self.hilight(textbg, (230, 230, 255), 0.2) text_dis = self.desaturate(textbg) self.images = (textbg, text_sel, text_dis) Text.rendered[str] = self.images class Item: """Item abstracts the menu items with some convenience 'functions.' In particular, accessing icon, title, and desc are merely item.icon, etc.""" def __init__(self, kwargs): self.__dict__['_attr'] = kwargs def __getattr__(self, attr): try: return self.__dict__['_attr'][attr] except KeyError: raise AttributeError("Item instance has no attribute '%s'"%attr) def __setattr__(self, attr, val): if self._attr.has_key(attr): orig = self.__dict__['_attr'][attr] if orig is None or isinstance(val, type(orig)): self.__dict__['_attr'][attr] = val else: raise ValueError("Item attribute '%s' must be of type '%s'" % (attr,type(orig))) else: raise AttributeError("Item instance has no attribute '%s'"%attr) class GameMenu: """GameMenu is the base which holds the common data for all menus""" def __init__(self, copyMenu=None): if copyMenu: self.items = copyMenu.items[:] self.ids = copyMenu.ids self.selectstyle = copyMenu.selectstyle self.selectevents = copy.copy(copyMenu.selectevents) self.eventtypecache = copy.copy(copyMenu.eventtypecache) else: self.items = [] self.ids = {} self.selectstyle = None self.selectevents = {} self.eventtypecache = [] def add_item(self, id, **kwargs): """add_item(self, id, **kwargs) -> None Add a menu item. Suggested keywords include: icon, text, desc, submenu, visible, enabled""" if self.ids.has_key(id): raise KeyError("Menu already has item with id '%s'"%id) kwargs['id'] = id # apply defaults to any missing standard arguments for kw in 'visible', 'enabled': if not kwargs.has_key(kw): kwargs[kw] = 1 for kw in 'submenu', 'icon': if not kwargs.has_key(kw): kwargs[kw] = None for kw in 'text', 'desc': if not kwargs.has_key(kw): kwargs[kw] = '[no %s]'%kw # convert desc if it's not a Desc if not isinstance(kwargs['desc'], Desc): kwargs['desc'] = Desc(kwargs['desc']) # convert icon if not an Icon if not isinstance(kwargs['icon'], Icon): kwargs['icon'] = Icon(kwargs['icon']) # convert text if it's passed in as a string if not isinstance(kwargs['text'], Text): kwargs['text'] = Text(kwargs['text']) self.items.append(Item(kwargs)) self.ids[id] = self.items[-1] def del_item(self, *ids): """del_item(self, *ids) -> None Remove a menu item(s)""" self.items = [item for item in self.items if item.id not in ids] for id in ids: del self.ids[id] def item(self, id): """item(self, id) -> menu item Return a reference to menu item matching id""" return self.ids[id] def run(self): raise NotImplementedError def get_event(self): """get_event(self) -> menu event Return a menu event based on handlers set in set_events. This will be one of 'select', 'cancel', 'next', 'prev', or None if a QUIT event is received.""" if not self.eventtypecache: typecache = {} for menuevent in self.selectevents.values(): for handle in menuevent: typecache[handle[0]] = handle[0] types = typecache.keys() del typecache self.eventtypecache = types else: types = self.eventtypecache menuevent = None eventhandlers = self.selectevents.items() if self.selectstyle == 'hover': while not menuevent: event = pygame.event.wait() if event.type == pygame.MOUSEMOTION: self.hoverpos = copy.copy(event.pos) menuevent = 'hover' elif event.type in types: for mevent, handlers in eventhandlers: for type, attr, val in handlers: if event.type==type and getattr(event, attr)==val: menuevent = mevent else: while not menuevent: event = pygame.event.wait() if event.type in types: for mevent, handlers in eventhandlers: for type, attr, val in handlers: if event.type==type and getattr(event, attr)==val: menuevent = mevent return menuevent def get_hover_coords(self): """get_hover_coords(self) -> (x,y) return the current hover position, valid only after a get_event() returns 'hover'""" return self.hoverpos def set_style(self, style, params=None): """set_style(self, style, params=None) -> None sets the select style for the menu to one of: hover, event. the default is a mouse hover with a left-click release over the drawn image. params is an optional dictionary containing elements described in set_select_params()""" self.selectstyle = style if params: self.params = params def set_events(self, menuevent, *events): """set_events(self, menuevent, *events) -> None sets the events that generate a given menu event. if *events is None, the menuevent is disabled. otherwise each event should be a tuple of the form: (<EventType>, <keyword>, <val>). an example tuple (MOUSEBUTTONUP, 'button', 1) describes a menuevent generation on the release of the left mouse button. valid menuevent values are the strings: select, cancel, next, prev""" if menuevent not in ('select', 'cancel', 'next', 'prev'): raise KeyError("menuevent '%s' is not valid"%menuevent) if not events: del self.selectevents[menuevent] else: self.selectevents[menuevent] = list(events) def set_items_enable(self, enable, *ids): """set_items_enable(self, enable, *ids) -> None sets 'enabled' to enable for all ids. enabled items (enable=1) should be selectable; disabled items (enable=0) should be visible but cannot be selected""" for id in ids: try: self.ids[id].enabled = enable except KeyError: pass # allow None as a valid id def set_items_visible(self, visible, *ids): """set_items_visible(self, visible, *ids) -> None sets 'visible' to visible for all ids. visible items (visible=1) should be drawn; invisible items (visible=0) should not be drawn and hence skipped over in the selection process as well""" for id in ids: try: self.ids[id].visible = visible except KeyError: pass # allow None as a valid id def set_item_icon(self, id, icon): """set_item_icon(self, id, icon) -> None sets item's icon for the given id. icon should be an Icon or a string holding the path to an icon file.""" if not isinstance(icon, Icon): icon = Icon(icon) self.ids[id].icon = icon def set_item_desc(self, id, desc): """set_item_desc(self, id, desc) -> None sets item's title for the given id. desc should be a Desc or a string""" if not isinstance(desc, Desc): desc = Desc(desc) self.ids[id].desc = desc class RadialMenu(GameMenu): """Base class for radial menus; not a functional menu itself""" def __init__(self, copyMenu=None): GameMenu.__init__(self, copyMenu) if not copyMenu: self.rotatecount = 0 self.rotateradius = (100, 100) self.skewalongaxis = (0, 0) else: self.rotatecount = copyMenu.rotatecount self.rotateradius = copyMenu.rotateradius self.skewalongaxis = copyMenu.skewalongaxis def set_radius(self, radius): """set_radius(self, radius) -> none Choose the radius in pixels for the rotating menu. radius can either be a single number, or a tuple of (x,y) radii for an axially aligned ellipse.""" if isinstance(radius, type(0.0)) or isinstance(radius, type(0)): self.rotateradius = (radius, radius) elif isinstance(radius, type(())) or isinstance(radius, type([])): if len(radius) == 2: self.rotateradius = tuple(radius) else: raise ValueError("set_radius requires a tuple of length 2") else: raise ValueError("set_radius requires a number or a length 2 tuple") def set_rotatecount(self, count): """set_rotatecount(self, count) -> None Set the selected item location in counter-clockwise spots from the top center""" self.rotatecount = float(count) def set_skew(self, axis): """set_skew(self, axis) -> None Choose the radius in pixels for the rotating menu. radius is a tuple of (x,y) skew factors.""" if isinstance(axis, type(())) or isinstance(axis, type([])) and len(axis)==2: self.skewalongaxis = tuple(axis) else: raise ValueError("set_skew requires a length 2 tuple") class RotatingIconMenu(RadialMenu): """RotatingIconMenu implements a menu system like Secret of Mana's""" def __init__(self, copyMenu=None): RadialMenu.__init__(self, copyMenu) if not copyMenu: self.set_style('event') KBD = pygame.KEYDOWN self.set_events('select', (KBD, 'key', pygame.K_RETURN)) self.set_events('next', (KBD, 'key', pygame.K_RIGHT)) self.set_events('prev', (KBD, 'key', pygame.K_LEFT)) self.set_events('cancel', (KBD, 'key', pygame.K_ESCAPE)) def run(self, center, screen): """run(self, center, screen) -> Selection Display the Rotating Icon Menu centered around center and handle events until the menu is cancelled or an item is selected. Return None or the Item correspondingly""" from math import pi, sin, cos try: cx, cy = center except ValueError: raise ValueError("center must be a tuple of (x, y)") except TypeError: raise ValueError("center must be a tuple of (x, y)") try: screencopy = pygame.Surface( (screen.get_width(), screen.get_height()), pygame.HWSURFACE, screen) screencopy.blit(screen, (0,0)) except AttributeError: raise ValueError("screen must be a surface") if screen.get_flags() & pygame.DOUBLEBUF: draw = self._drawdoublebuf else: draw = self._drawsinglebuf items = [item for item in self.items if item.visible ] nitems = len(items) incr = 1/16.0 rx, ry = self.rotateradius sx, sy = self.skewalongaxis rot = self.rotatecount try: angle = 2*pi/nitems except ZeroDivisionError: raise ValueError("No menu items defined") sel = 0 osel = 0 cur = sel - incr opos = None do_exit = 0 chose = None while not do_exit: while abs(sel-cur)>=incr: if cur < sel: cur += incr if cur > sel: cur -= incr angles = [angle*(i-cur-rot) for i in range(nitems)] pos = [(cx + rx*sin(t) + sx*cos(t), cy - ry*cos(t) + sy*sin(t)) for t in angles] draw(screen, screencopy, items, pos, opos, sel, osel) opos = pos[:] osel = sel event = self.get_event() if event == 'next': sel = (sel + 1) % nitems if sel < cur: cur -= nitems elif event == 'prev': sel = (sel + nitems - 1) % nitems if sel > cur: cur += nitems elif event == 'cancel' or event == None: do_exit = 1 elif event == 'select': if items[sel].enabled: try: chose = items[sel] except IndexError: chose = None do_exit = 1 if opos: draw(screen, screencopy, items, None, opos, sel, osel) try: return chose.id except AttributeError: return None def _drawsinglebuf(self, screen, back, items, pos, opos, sel, osel): """_drawsinglebuf(self, screen, back, items, pos, opos, sel, osel) -> None Draws items to a single-buffer screen with corresponding positions (if passed), erasing old positions (if passed).""" dirtyrects = [] # remove all old stuff if positions passed if opos: # clear all icons for item, op in zip(items, opos): w, h = item.icon.size rect = (op[0]-w/2, op[1]-h/2, w, h) screen.blit(back, op, rect) dirtyrects.append(rect) # clear description rect = items[osel].desc.rect screen.blit(back, rect) dirtyrects.append(rect) # draw all new stuff if posistions passed if pos: # draw a hilight around the selected item #p = pos[sel] #w, h = items[sel].icon.size #pygame.draw.rect(screen, (100,255,100), (p[0]-w/2, p[1]-h/2, w, h), 1) # draw all items for item, p, k in zip(items, pos, range(len(items))): icon = item.icon # center the icon around pos w, h = icon.size p = (p[0]-w/2, p[1]-h/2) image = (item.enabled and [k==sel] or [2])[0] screen.blit(icon.get_image(image), p, icon.rect) dirtyrects.append((p[0], p[1], w, h)) screen.blit(items[sel].desc.get_image(), (0,0)) dirtyrects.append(items[sel].desc.rect) pygame.display.update(dirtyrects) def _drawdoublebuf(self, screen, back, items, pos, opos, sel, osel): """_drawdoublebuf(sel, screen, back, items, pos, opos, sel, osel) -> None Draws items to a double-buffer screen with corresponding positions (if passed), erasing old positions (if passed).""" if opos: screen.blit(back, (0,0)) if pos: # draw all icons for item, p, k in zip(items, pos, range(len(items))): icon = item.icon # center the icon around pos w, h = icon.size p = (p[0]-w/2, p[1]-h/2) image = (item.enabled and [k==sel] or [2])[0] screen.blit(icon.get_image(image), p, icon.rect) # draw selected item's description screen.blit(items[sel].desc, (0,0)) pygame.display.flip() class CircleTextMenu(RadialMenu): """CircleTextMenu implements a menu system like The Sims uses (minus the floating head)""" def __init__(self, copyMenu=None): RadialMenu.__init__(self, copyMenu) if not copyMenu: self.set_style('hover') KBD = pygame.KEYDOWN self.set_events('select', (pygame.MOUSEBUTTONUP, 'button', 1)) self.set_events('cancel', (KBD, 'key', pygame.K_ESCAPE), (pygame.MOUSEBUTTONDOWN,'button',3)) def run(self, center, screen): """run(self, center, screen) -> Selection Display the Circle Text Menu centered around center and handle events until the menu is cancelled or an item is selected. Return None or the Item correspondingly""" from math import pi, sin, cos try: cx, cy = center except ValueError: raise ValueError("center must be a tuple of (x, y)") except TypeError: raise ValueError("center must be a tuple of (x, y)") try: screencopy = pygame.Surface( (screen.get_width(), screen.get_height()), pygame.HWSURFACE, screen) screencopy.blit(screen, (0,0)) except AttributeError: raise ValueError("screen must be a surface") if screen.get_flags() & pygame.DOUBLEBUF: draw = self._drawdoublebuf else: draw = self._drawsinglebuf items = [item for item in self.items if item.visible] nitems = len(items) incr = 1/16.0 rx, ry = self.rotateradius sx, sy = self.skewalongaxis rot = self.rotatecount try: angle = 2*pi/nitems except ZeroDivisionError: raise ValueError("No menu items defined") sel = None osel = None do_exit = 0 chose = None angles = [angle*(i-rot) for i in range(nitems)] pos = [(cx + rx*sin(t) + sx*cos(t) - s[0]/2, cy - ry*cos(t) + sy*sin(t) - s[1]/2) for t, s in zip(angles, [i.text.size for i in items])] trect = [pygame.Rect(p[0], p[1], t[0], t[1]) for p, t in zip(pos, [i.text.size for i in items])] while not do_exit: draw(screen, screencopy, items, pos, sel, osel) osel = sel event = self.get_event() if event == 'cancel' or event == None: do_exit = 1 elif event == 'hover': x,y = self.get_hover_coords() for i, rect in zip(range(nitems), trect): if rect.collidepoint(x,y): sel = i break else: sel = None elif event == 'select' and sel is not None: if items[sel].enabled: try: chose = items[sel] except IndexError: chose = None do_exit = 1 draw(screen, screencopy, items, pos, sel, osel, clear=1) try: return chose.id except AttributeError: return None def _drawsinglebuf(self, screen, back, items, pos, sel, osel, clear=0): """_drawsinglebuf(self, screen, back, items, pos, sel, osel, clear=0) -> None Draws items to a single-buffer screen with corresponding positions or erase if clear=1.""" dirtyrects = [] # remove all old stuff if clear if clear: for item, op in zip(items, pos): text = item.text rect = (op[0], op[1], text.width, text.height) screen.blit(back, op, rect) dirtyrects.append(rect) if osel is not None: rect = items[osel].desc.rect screen.blit(back, rect) dirtyrects.append(rect) # draw all new stuff if not clear else: for item, p, k in zip(items, pos, range(len(items))): image = (item.enabled and [k==sel] or [2])[0] text = item.text screen.blit(text.get_image(image), p, text.rect) dirtyrects.append((p[0], p[1], text.width, text.height)) if sel != osel: if osel is not None and items[osel].enabled: rect = items[osel].desc.rect screen.blit(back, (0,0), rect) dirtyrects.append(rect) if sel is not None and items[sel].enabled: screen.blit(items[sel].desc.get_image(), (0,0)) dirtyrects.append(items[sel].desc.rect) pygame.display.update(dirtyrects) def _drawdoublebuf(self, screen, back, items, pos, sel, osel, clear=0): """_drawdoublebuf(self, screen, back, items, pos, sel, osel, clear=0) -> None Draws items to a double-buffer screen with corresponding positions or erase if clear=1.""" if clear: screen.blit(back, (0,0)) else: for item, p, k in zip(items, pos, range(len(items))): image = (item.enabled and [k==sel] or [2])[0] screen.blit(item.text.get_image(image), p, item.text.rect) screen.blit(items[sel].desc.get_image(), (0,0)) pygame.display.flip() def _main(): """Test the functionality of the menus if run as a program""" pygame.display.init() pygame.font.init() screen = pygame.display.set_mode((640, 480), pygame.DOUBLEBUF) for i in range(239, -1, -1): screen.fill((i/2,i/2,i), (240-i, 240-i, 160+i+i, i+i)) pygame.display.flip() # test the circle text menu (The Sims style) cmenu = CircleTextMenu() cmenu.set_radius((150,100)) cmenu.set_rotatecount(0) for id, text, desc in (('apple', 'Apple', 'Red Apple'), ('banana', 'Banana', 'Yellow Banana'), ('canteloupe', 'Canteloupe', 'Yummy Melon'), ('devil', 'Devil food cake', 'Mmm'), ('entropy', 'Entropy', 'Increasing Disorder'), ('fig', 'Fig Newton', 'Breaded outsides, sticky insides'), ('game', 'Game', 'Written in Pygame so it rocks!'), ): cmenu.add_item(id, text=text, desc=desc) c = cmenu.run((320,240), screen) cmenu.set_items_enable(0, c) print c # test the rotating Icon menu (Secret of Mana style) rmenu = RotatingIconMenu() rmenu.set_radius((150,100)) rmenu.set_skew((40,-40)) try: rmenu.add_item('rapple', icon='/usr/share/pixmaps/apple-red.png', desc='Apple: restores energy') rmenu.add_item('gapple', icon='/usr/share/pixmaps/apple-green.png', desc='Apple: restores mana') rmenu.add_item('slashdot', icon='/usr/share/pixmaps/slashapp.png', desc='Slashdot: reduces intelligence') rmenu.add_item('gimp', icon='/usr/share/pixmaps/gnome-gimp.png', desc='Wilber: raises creativity') rmenu.add_item('earth', icon='/usr/share/pixmaps/gnome-globe.png', desc='Earth: raises continence') rmenu.add_item('joystick', icon='/usr/share/pixmaps/gnome-joystick.png', desc='Joystick: increases control') rmenu.add_item('paintbrush', icon='/usr/share/pixmaps/gnome-graphics.png', desc='Paintbrush: lowers cleanliness') for i in 'abcdefg': rmenu.add_item(i, icon='/usr/share/pixmaps/sawfish-misc.png', desc='yo '+i*3) except pygame.error: print """ Chances are one or more of the icons in the menu.add_item() lines aren't available. Try changing them to icons that exist on your system and/or commenting some of them out. """ raise SystemExit("Probably Missing Icons") rmenu.set_items_enable(0, 'paintbrush', 'a', 'c', 'e') print rmenu.run((320,240), screen) cmenu.set_radius((100,50)) cmenu.set_rotatecount(0.5) print cmenu.run((200,100), screen) if __name__ == '__main__': _main()