[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

Re: [pygame] GSoC



Any GUI-widget toolkit needs to send information back to the application about the events that happen: button clicks, menu items picked, text entered, etc..  There are two obvious ways to do that in pygame: callbacks and pygame events.

Pygame events are by far the better approach.

Here's how I do it in my code.  When initializing a GUI-widget, it is passed an identifying data value (an ID), to distinguish it from other GUI-widgets.  The ID value is stored in the GUI-widget's instance data.  Then, when the widget needs to send information back to the program, it simply posts a pygame event with the ID value stored in the pygame event instance.  (I currently use a single event type number, default USEREVENT, for all GUI-widget-generated events.)

This is super-simple for the application to handle, much easier than callbacks.  In the main program, you just have another elif clause in the event loop, to receive GUI-widget events: button clicks, menu clicks, user-entered-text-and-pressed [Enter], etc.

Using the event queue like that is just plain Better for the structure of the program than using callbacks.

If you use callbacks to call user functions whenever a button or menu click happens, then much of the time all the callbacks do is set some global flags, store some data, and then return, so that you can unwrap the call stack and wind your way back out of the innards of the GUI code, before actually doing whatever the button or menu click requires.  (That's because, if you are an experienced programmer, you have learned [probably the hard way] that you really don't want to start anything complicated in response to a click if you're deep in the bowels of the GUI code [which has called your callback function], since the complicated thing you start might want to use that same GUI code, and get button clicks and menu choices of its own, leading to ever deeper recursion in the [hopefully reentrant!] GUI code, and mind-bending complexity and bugs.)

If you use the event queue, instead of callbacks, to pass back information from the GUI, all that complexity goes away.  A button click, a menu selection, etc. is just another event to be handled in the main event loop.

Python/pygame makes that easy, since you can attach as much additional info as you want to the event object.  I think the reason so many other GUI frameworks use callbacks is that their event queues are so limited.  All they contain is integer event numbers, so it isn't feasible to pass back (for example) the string of text from a text entry field.  So they work around this limitation with callbacks to pass back the additional information.  Programmers get used to this cumbersome approach, and do it that way even when they don't have to, because of the Toolbox Principle: "if all you have is a hammer, then everything looks like a nail."

Additionally, you can make any GUI-widget modal, by wrapping it in a simple function that contains a temporary event loop.  Here's mine:

def modal_popup(widget):
    '''Usually, widget sprites are displayed and active along with everything
    else in the application, but it is also possible to display a widget sprite
    "modally," that is, with everything else suspended.  To do so, just pass
    the widget to this function.  It won't return until the widget generates
    a result event.

    A result event is any event of type WIDGETEVENT which has an attribute
    .internal=False.

    Note: don't use this with widget sprites which can't generate events (like
    LabelSprites), lest it never return!  (Humming Kingston Trio tune...)
    '''
    group = WidgetGroup(widget)  # a temporary group, for duration of this popup
    keep_running = True
    while keep_running:
        # Update and Draw the widget
        group.update()
        group.draw(screen)
        pygame.display.update()
        # get next event:
        event = pygame.event.wait()
        # If event is QUIT then they closed the applicaiton, so quit
        if event.type == QUIT:
            keep_running = False
            pygame.quit()
            sys.exit(0)
        # Give widget sprite a look at the event
        group.notify(event)
        # We loop until we get a result event from the widget!
        if event.type == WIDGETEVENT:
            if hasattr(event,'internal') and not event.internal:
                group.remove(widget)
                keep_running = False
    return event

However, I have noticed that pygame events are somewhat weird.  They are objects, but they don't always behave like proper Python objects.  There's no __dict__ attribute, and dir() doesn't work, which I find annoying:

Python 3.1.3 (r313:86834, Nov 27 2010, 18:30:53) [MSC v.1500 32 bit (Intel)] on win32
Type "copyright", "credits" or "license()" for more information.
>>> import pygame
>>> ev = pygame.event.Event( pygame.USEREVENT, {'id':'test', 'internal':False} )
>>> dir(ev)
[]
>>> ev.__dict__
Traceback (most recent call last):
  File "<pyshell#6>", line 1, in <module>
    ev.__dict__
AttributeError: event member not defined
>>> ev.type
24
>>> ev.id
'test'
>>> ev.internal
False
>>> repr(ev)
"<Event(24-UserEvent {'internal': False, 'id': 'test'})>"
>>> 

Contrast that with how other Python objects behave:

>>> class junk:
fee = 1
fie = True
foe = 'giant'
fum = (1,2,3)

>>> j1 = junk()
>>> repr(j1)
'<__main__.junk object at 0x034FA870>'
>>> dir(j1)
['__class__', '__delattr__', '__dict__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'fee', 'fie', 'foe', 'fum']


See what I mean?  With other Python objects, it is easy to enumerate the attributes of an object.  But it is not obvious how to do that for pygame event objects (though repr() somehow manages it).

Does anyone know how repr() manages to find the attributes for the pygame event, since dir() doesn't work?

Dave


On Sat, Feb 5, 2011 at 7:00 PM, Greg Ewing <greg.ewing@xxxxxxxxxxxxxxxx> wrote:
Sam Bull wrote:

But, from a conversation with someone else about this, it was suggested
that this could complicate the game code, and it would be better to use
events in order to keep the game code and GUI code separated.

It's not necessary to use pygame events to achieve that. Techniques
for keeping GUI and application code decoupled are well known. Google
"MVC" for more information.

The only good reason I can think of for using events this way is if
for some reason you need to handle the event asynchronously, i.e. you
need to ensure that all processing for the current event is completed
before carrying out the action. Otherwise, using an event is probably
just needless complication.

--
Greg