Skip to main content

A simple and basical Pygame library for fast develop of menu interfaces

Project description

KezMenu

A simple and basical Pygame based module for a fast development of menu interfaces.

Introduction

This module is based on the original work of PyMike, from the Pygame community (see the EzMeNu project for more info). As I found some issues using the Mike’s version library, I release this one that fix some of theme, and also add features.

What you can do with this?

You can easilly draw a menu interface, and selecting an option using the mouse of arrow keys.

Examples and usage

Here a fully example of use of this library. Even if we use the Python doctest format, this isn’t a politically correct automated test because we’ll wait for user input and no real tests are done onto results (no… this is not true, but none of the major feature are really tested here).

Maybe someday I’ll complete this with better python tests!

However… The code in this page is a working example. If you know nothing about doctests, only know that you can run this code simply downloading the source, going to the kezmenu directory and type:

python tests.py

If you have the library installed on your system you can run the example program from the python interpreter:

import kezmenu

kezmenu.runTests()

Init all the Pygame stuff

First of all we need to enable the Pygame environment:

>>> import pygame
>>> from pygame.locals import *
>>> import pygame.font
>>> pygame.font.init()
>>> screen = pygame.display.set_mode((640,480), 0, 32)

KezMenu works drawing the menu onto a pygame.Surface instance; the better (simpler) choice is always draw the menu onto the screen (another Surface instance obtained using methods of the pygame.display Pygame’s module, like pygame.display.set_mode), because is not possible in Pygame to know the offset of a blitten surface from the screen topleft corner.

This is not important if you are not planning to use the mouse on the menu and rely only up and down keys. To disable the mouse, just put to False the mouse_enabled attribute of a KezMEnu instance.

In the first example below we will use a surface inside the screen for drawing the menu. So we create this surface first:

>>> surface = pygame.Surface( (400,400), flags=SRCALPHA, depth=32 )
>>> surface.fill( (150,150,150,255) )
<rect(0, 0, 400, 400)>

Now we can blit the surface on the screen. We will repeat this procedure some times so it’s better create out first dummy function (those functions aren’t really useful outside this test environment):

>>> def blitSurface():
...     screen.blit(surface, (50,50) )
...     pygame.display.update()

So we can call it for the first time:

>>> blitSurface()

This is a graphical test, so we need to delay the automatic actions and make possible that the user can look at results and then go over. We wait for user input before going over.

To do this we create a second silly function that we’ll call often later:

>>> click_count = 0
>>> def waitForUserAction(msg='???'):
...     global click_count
...     click_count+=1
...     pygame.display.set_caption("Example %s - %s" % (click_count, msg))
...     while True:
...         for event in pygame.event.get():
...             if event.type==KEYDOWN:
...                 return

Ok, lets call it for the first time:

>>> waitForUserAction("An empty, dark surface")

We only displayed on the screen a surface filled with a dark color.

The KezMenu

Now it’s time to create our KezMenu instance:

>>> from kezmenu import KezMenu
>>> menu = KezMenu()

To draw out menu we create before another second dummy function for test needs:

>>> def drawMenu():
...     surface.fill( (150,150,150,255) )
...     menu.draw(surface)
...     blitSurface()
...     pygame.display.flip()

This is a valid way to create our menu, but we only obtain an empty menu (so invisible to user).

>>> drawMenu()
>>> waitForUserAction("You see no difference")

You see no changes from the example 1, isn’t it? If we create our menu in this way we need to fill it runtime with our options. You can do this by modifying runtime the options attribute.

We need to know what the menu action must execute, before defining the option:

>>> option_selected = None
>>> def option1():
...     global option_selected
...     option_selected = 1
>>> menu.options = ( {'label': 'First option!', 'callable': option1}, )

As you can see the options member must be a tuple of dictionary. Those dict must contains (at least) the label and callable parameters; other paramter can be specified for advanced use (see EFFECTS.txt).

The label is the string to be draw for the menu option, and the callable is the function, object, method, … to be called on selection.

As far as we blitted the menu inside a surface that isn’t in the (0,0) screen position, we need (if we wanna use also mouse control later) to specify the screen_topleft_offset attribute:

>>> menu.screen_topleft_offset = (50,50)
>>> drawMenu()
>>> waitForUserAction("Our first option!")

In this way we say to the menu that all coordinates must keep count of an offset from the topleft corner of the screen.

Pass a screen object directly to the menu.draw method is more common, so in all other examples we will drop the use of the surface object.

>>> surface = None

Customize the menu GUI: colors, font, …

The menu showed in the last example is a little ugly. Too near the the screen border and color used for non selected elements are ugly. You can modify those properties also for an already created menu:

>>> menu.position
(0, 0)
>>> menu.position = (30,50)
>>> menu.position
(30, 50)
>>> drawMenu()
>>> waitForUserAction("Not soo near to border now...")

Now the menu is in a better position on the screen.

Lets go over and modify some font properties, but first let me talk about the menu dimension. Data about the menu dimension is always available using the width and height attributes:

>>> menu.width
126
>>> menu.height
115

Those values are related to the labels displayed in the menu voices and also influenced by the font used (and it’s dimension):

>>> new_font = pygame.font.Font(None, 38)
>>> menu.font = new_font
>>> drawMenu()
>>> waitForUserAction("Bigger font")

This bigger font has different size, so the whole menu size raise:

>>> menu.width
154
>>> menu.height
135

Now colors:

>>> menu.color = (255,255,255,100)
>>> menu.focus_color = (255,255,0)
>>> drawMenu()
>>> waitForUserAction("...and better colors")

As you can see we can easily manipulate the font color, and the font of the selected item.

Do something useful with our KezMenu

You noticed that our previous examples are rightnow static screenshots without any possible interaction.

To make some real examples with our menu we need to use the KezMEnu.update method, and pass it the pygame.Event instances that Pygame had captured. The waitForUserAction dummy function is no more needed because a menu is commonly a way to wait for user decision itself.

>>> click_count+=1
>>> pygame.display.set_caption("Example %s - %s" % (click_count, "Use the KezMenu freely"))
>>> while True:
...     events = pygame.event.get()
...     menu.update(events)
...     drawMenu()
...     if option_selected:
...         break
>>> option_selected is not None
True

The option_selected variable now contains the return value of the callable, relative to the option choosen.

NB: if you select the Quit option running this test you will get a fake test failure. This isn’t a KezMenu bug, but it’s normal in Python tests: the sys.exit call raise a SystemExit exception that in tests are handled in a different way.

Ok, the example is at the end!

But KezMenu has also some effects! You can find examples about menu effects in the EFFECTS.txt file!

Goodbye!

>>> pygame.quit()

Play with the KezMenu’s effects

Introduction

From version 0.3.0 the inner KezMenu structure is changed a lot. One of the first news is that every line has it’s own pygame.font.Font to use.

This will give us a lot of freedom for menu’s entries display effects.

>>> import pygame
>>> from pygame.locals import *
>>> import pygame.font
>>> pygame.font.init()
>>> screen = pygame.display.set_mode((640,480), 0, 32)
>>> screen.fill( (50,50,50,255) )
<rect(0, 0, 640, 480)>
>>> click_count = 0
>>> def waitForUserAction(msg='???'):
...     global click_count
...     click_count+=1
...     pygame.display.set_caption("Example %s - %s" % (click_count, msg))
...     while True:
...         for event in pygame.event.get():
...             if event.type==KEYDOWN:
...                 return
>>> def updateCaption(msg='???'):
...     global click_count
...     click_count+=1
...     pygame.display.set_caption("Example %s - %s" % (click_count, msg))
>>> from kezmenu import KezMenu
>>> def drawMenu():
...     screen.fill( (50,50,50,255) )
...     menu.draw(screen)
...     pygame.display.flip()
>>> option_selected = 0
>>> def optSelected():
...     global option_selected
...     option_selected=1
>>> menu = KezMenu(
...            ["First option!", optSelected],
...            ["sEcond", optSelected],
...            ["Again!", optSelected],
...            ["Lambda", optSelected],
...            ["Quit", optSelected],
...        )
>>> menu.font = pygame.font.Font(None, 20)
>>> menu.color = (255,255,255)
>>> menu.position = (10,10)

Lets show the actual menu height:

>>> menu.height
70

Here again a standard menu.

>>> drawMenu()
>>> waitForUserAction("The same boring menu")

Now we want a bigger font for ‘sEcond’ entry:

>>> menu.options[1]['font'] = pygame.font.Font(None, 26)
>>> drawMenu()
>>> waitForUserAction("Bigger entry 2")

Lets who now that manually play with the options menu can lead to some errors in the menu itself, because KezMenu instance is not warn of changed parameters:

>>> menu.height
70

So even if we display a new well drawn menu, the saved size is not changed. This is bad. We can fix this simply calling an internal KezMenu method, that commonly KezMenu objects call for us:

>>> menu._fixSize()
>>> menu.height
74

This introduction was only a taste of what there’s inside KezMenu effect’s ways to do things.

The KezMenu available effects

Here a list and example of usage of all available effects. Effects are enabled using the enableEffect method, and must be used for existing effects, or a KeyError is raised:

>>> menu.enableEffect('not-existing-effect-just-for-raise-error')
Traceback (most recent call last):
...
KeyError: "KezMenu don't know an effect of type not-existing-effect-just-for-raise-error"

In all the following example we need a timer, and so can use the pygame.time.Clock:

>>> clock = pygame.time.Clock()

To enable an effect, we must use the enableEffect method, passing to it the name of the effect and optionally some keyword arguments.

Important thing: effects can be (sometimes) combined!

raise-line-padding-on-focus

This effect raise the padding above and below the focused element while time is passing. Padding on the last element will only raise the top padding. Padding on the first element will only raise the bottom padding.

padding

Default: 10px. The number of pixel that will be added above and below the selected menu entry.

enlarge_time

Default: 500 millisec. Time needed (in seconds) to reach the max padding.

>>> updateCaption('raise-line-padding-on-focus')
>>> option_selected = 0
>>> menu.enableEffect('raise-line-padding-on-focus')
>>> while True:
...     time_passed = clock.tick() / 1000.
...     events = pygame.event.get()
...     menu.update(events, time_passed)
...     drawMenu()
...     if option_selected:
...         break

We can call this effect with new custom values:

>>> updateCaption('raise-line-padding-on-focus (custom)')
>>> option_selected = 0
>>> menu.enableEffect('raise-line-padding-on-focus', padding=30, enlarge_time=1.)
>>> while True:
...     time_passed = clock.tick() / 1000.
...     events = pygame.event.get()
...     menu.update(events, time_passed)
...     drawMenu()
...     if option_selected:
...         break
>>> menu.disableEffect('raise-line-padding-on-focus')

raise-col-padding-on-focus

This effect raise the padding on the left of the focused element while time is passing.

padding

Default: 10px. The number of pixel that will be added on the left of the selected menu entry.

enlarge_time

Default: 500 millisec. Time needed (in seconds) to reach the max padding.

>>> updateCaption('raise-col-padding-on-focus')
>>> option_selected = 0
>>> menu.enableEffect('raise-col-padding-on-focus')
>>> while True:
...     time_passed = clock.tick() / 1000.
...     events = pygame.event.get()
...     menu.update(events, time_passed)
...     drawMenu()
...     if option_selected:
...         break

We can call this effect with new custom values:

>>> updateCaption('raise-col-padding-on-focus (custom)')
>>> option_selected = 0
>>> menu.enableEffect('raise-col-padding-on-focus', padding=20, enlarge_time=3.)
>>> while True:
...     time_passed = clock.tick() / 1000.
...     events = pygame.event.get()
...     menu.update(events, time_passed)
...     drawMenu()
...     if option_selected:
...         break
>>> menu.disableEffect('raise-col-padding-on-focus')

enlarge-font-on-focus

This effect will raise the font size of the selected element on the menu of a given multiply factor. The Font class of Pygame has a limitation (not a bug): is not possible to obtain the font data (family, size) after the font creation.

So for use this effect is needed to pass to the init method all font data, and a new font will be created (the standard menu ‘font’ property will be overrided).

font

Required. A font name of path, same as you are passing it to Pygame Font constructor, so can also be None.

size

Required. The size of the font, same as you are passing it to Pygame Font constructor.

enlarge_factor

Default: 2.(200%). The multiply factor of the font size at the maximum extension, as a real value.

enlarge_time

Default: 500 millisec. Time needed (in seconds) to reach the max font size.

>>> updateCaption('enlarge-font-on-focus')
>>> option_selected = 0
>>> menu.enableEffect('enlarge-font-on-focus', font=None, size=18)
>>> while True:
...     time_passed = clock.tick() / 1000.
...     events = pygame.event.get()
...     menu.update(events, time_passed)
...     drawMenu()
...     if option_selected:
...         break

Lets now customized all the data:

>>> updateCaption('enlarge-font-on-focus (focus)')
>>> option_selected = 0
>>> menu.enableEffect('enlarge-font-on-focus', font=None, size=18, enlarge_factor=5., enlarge_time=2.)
>>> while True:
...     time_passed = clock.tick() / 1000.
...     events = pygame.event.get()
...     menu.update(events, time_passed)
...     drawMenu()
...     if option_selected:
...         break
     >>> menu.disableEffect('enlarge-font-on-focus')

Combining KezMenu effects

The primary scope of KezMenu effects is to be enough flexible to be activated in same time. As the effects available will raise in future, sometimes can be that effects will fall in conflict each other, but in general I’ll try to integrate them.

Activate more that one effects in the same time is very simple: just activate it!

>>> updateCaption('Combined effects example')
>>> option_selected = 0
>>> menu.enableEffect('raise-line-padding-on-focus', padding=30, enlarge_time=1.)
>>> menu.enableEffect('raise-col-padding-on-focus', padding=20, enlarge_time=1.)
>>> menu.enableEffect('enlarge-font-on-focus', font=None, size=16, enlarge_factor=3.)
>>> while True:
...     time_passed = clock.tick() / 1000.
...     events = pygame.event.get()
...     menu.update(events, time_passed)
...     drawMenu()
...     if option_selected:
...         break
>>> menu.disableEffect('raise-line-padding-on-focus')
>>> menu.disableEffect('raise-col-padding-on-focus')
>>> menu.disableEffect('enlarge-font-on-focus')

Wanna write a new effect?

If anyone is interested in develop a new effect, I will be happy to integrate it in KezMenu!

Changelog

0.3.6 (2013-06-07)

  • Fixed packaging error

0.3.5

  • Test done with Python 2.6 and Pygame 1.9

  • Fixes to doctest

  • Removed deprecated code

0.3.1

  • Nothing new, but fixed a problem that break menus if the module is used in a py2exe environment.

0.3.0

  • Added a README.txt in doctest format.

  • Added many deprecation warning for not PEP 8 named methods. Those methods will be removed in future.

  • If the menu position was moved from (0,0), the mouse control was buggy because not counting this offset.

  • Support for the menu effects

0.2.1

  • Released as egg.

  • Added some txt file.

0.2.0 - Unreleased

  • Fixed many issues related to font handle.

  • Added the support to mouse usage.

0.1.0

For version 0.1.0 I mean the original EzMeNu version 1.0.

Credits

  • PyMike from the Pygame community for his original work.

TODO

  • Submenus?

  • More effects (ideas are welcome)

  • Need to work better with transparency

Get the code

The source repository is hosted at GitHub

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

KezMenu-0.3.6.tar.gz (17.3 kB view hashes)

Uploaded Source

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page