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.
# gizmos - a very minimal gui widget set for the things/pygame libraries


A very minimal set of GUI widgets for the "things" framework and pygame.


    Some games want to have a very customized look for their dialog boxes
    or other onscreen controls.  Some applications using pygame cannot
    also use a more traditional widget toolkit such as GTK or Tk or Qt.
    This module is intended to fill these needs in a lightweight way.

    A "gizmo" is similar to a widget or control or field on a dialog box.

    Many gizmos are given direct access to simple variables by reference
    and apply their value changes immediately.  No need to copy and
    access the values in and out of most gizmo types.

    A "slate" is akin to a dialog box or other frame window.

    Typically, one or more gizmos are set to follow a slate, then the
    slate is set to be a hyperspace (a space drawn atop another) for the
    current application space.  When the interaction on the slate has
    been done, it segues to nothing, which makes it disappear.

    Slates and gizmos cooperate to figure out the space required, similar
    to the way most widget toolkits allow for layout and packing.


    Ed Halley ( 6 November 2007


import goals ; from goals import *
import things ; from things import *
import pygame ; from pygame.locals import *


class Gizmo (Thing):
    def __init__(self):
        super(Gizmo, self).__init__() = None
        self.caption = ''
        self.variable = None
        self.minsize = [20, 10]
        self.maxsize = [800, 480]
    def set_id(self, id): = id
    def set_caption(self, text):
        self.caption = str(text)
    def set_variable(self, variable):
        self.variable = variable
    def visit(self, context):
        # alter my followers' positions to avoid conflict
        # get my unionrect
        # add my margins

class TextGizmo (Gizmo):
    def __init__(self, text=''):
        super(TextGizmo, self).__init__()
        self.color = (0x00, 0x00, 0x00)
        self.dirty = True
    def set_text(self, text):
        self.text = str(text)
        self.dirty = True
    def append_text(self, text):
        self.text += str(text)
        self.dirty = True
    def visit(self, context):
        if not self.dirty:
    def layout(self, context):
        space =
        if not space: return
        font = getattr(space, 'font', None)
        if not font: return
        strings = self.text.split('\n')
        self.images = [ self.font.render(string, 1, self.color)
                        for string in strings ]
        self.rects = [ image.get_rect() for image in self.images ]
        self.minsize = [0,0]
        for i in range(len(self.images)):
            height = self.rects[i].height
            self.rects[i].top = self.minsize[1]
            self.minsize[0] = max(self.minsize[0], self.rects[i].width)
            self.minsize[1] += height
        self.dirty = False
    def paint(self, surface):
        for i in range(len(self.images)):
            surface.blit(self.images[i], self.rects[i].topleft)

class PressGizmo (Gizmo):
    def __init__(self, todo=None):
        super(PressGizmo, self).__init__()
        self.todo = todo
    def trigger(self):
        if callable(self.todo):
            todo = self.todo

class CheckGizmo (PressGizmo):
    def __init__(self, states=None):
        super(CheckGizmo, self).__init__()
        if states is None:
            states = [ 'off', 'on' ]
        self.states = states
        self.state = 0
    def set_state(self, state):
        self.state = state % len(self.states)
        return self.states[self.state]
    def get_state(self):
        return self.states[self.state]

class RadioGizmo (PressGizmo):
    def __init__(self):
        super(RadioGizmo, self).__init__()
    def get_peers(self):
        if not self.frame: return [ self ]
        cls = RadioGizmo
        id =
        f = self.frame.followers() # should get gizmos in tab order
        peers = [ p for p in f if isinstance(f, cls) and == id ]
        return peers

class ArrayGizmo (Gizmo):
    def __init__(self, rows=1, cols=1):
        super(ArrayGizmo, self).__init__() = {}
        self.just = {}
        self.reset(rows, cols)
    def add(self, gizmo, where=None):
        if where is None:
            if self.rows > 1 and self.cols == 1:
                where = (self.rows+1,1)
            elif self.rows == 1 and self.cols > 1:
                where = (1,self.cols+1)
                raise ValueError, 'must supply row/column of new gizmo'
    def resize(self, rows=1, cols=1):
        self.rows = rows
        self.cols = cols
        self.heights = [0,] * self.rows
        self.widths = [0,] * self.cols
        self.packed = False
    def pack(self):
        # visit every child to determine heights and widths
        self.packed = True
    def visit(self, context):
        if not self.packed: self.pack()
        # visit every child collecting their final positions


class Slate (Space):

    def __init__(self):
        super(Slate, self).__init__(VectorContext())
        self.font = App.resource('FreeSans.ttf', 16)
        self.timeout = V(0.)
        self.add_goal(TimeGoal(self.timeout, V(1.), dt=5.0))

    def event(self, event):
        super(Slate, self).event(event)

    def segue(self):
        if self.timeout[0] >= 1.:
            return None
        return super(Slate, self).segue()

    def background(self, surface):
        '''A slate has no background, as such.'''

    def visit(self, surface):
        super(Slate, self).visit(surface)

    def paint(self, surface):
        super(Slate, self).visit(surface)


class _TestSlate (Slate):
    def __init__(self):
        super(_TestSlate, self).__init__()
        self.color = (0xFF,0xFF,0x80)
        self.quit = False
    def modal(self):
        return True
    def segue(self):
        if self.quit: return None
        return self
    def paint(self, surface):
        super(_TestSlate, self).paint(surface), self.color, (200,200), 50)
    def event(self, event):
        super(_TestSlate, self).event(event)
        if event.type == KEYDOWN:
            if event.key == K_SPACE:
                print 'switch color'
                self.color = (self.color[1], self.color[2], self.color[0])
            if event.key == K_ESCAPE:
                self.quit = True

class _TestSpace (Space):

    def __init__(self, *args):
        super(_TestSpace, self).__init__(*args)
        self.text = [ "Press Space to see a slate.",
                      "Press Esc to quit." ]
        self.font = App.resource("FreeSans.ttf", 25)

    def show_slate(self):
        print "show_slate"
        t = _TestSlate()
        self.hyper = t

    def event(self, event):
        super(_TestSpace, self).event(event)
        if self.hyper and self.hyper.modal(): return
        if event.type == KEYDOWN:
            if event.key == K_SPACE:

    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) )
            surface.blit( text, (15, y) )
            y += text.get_rect().height

if __name__ == '__main__':
    app = App('Slates', (720, 420)) = _TestSpace()

Contact Ed Halley by email at
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.
Make donations with PayPal - it's fast, free and secure!