=======
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.
__ http://www.pygame.org/project/855/
`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.
__ http://www.pygame.org/docs/ref/surface.html
__ http://www.pygame.org/docs/ref/display.html
__ http://www.pygame.org/docs/ref/display.html#pygame.display.set_mode
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
Ways to link action to menu selection
-------------------------------------
Manually change the *options* attribute is simple (and can be useful) but the most common way is to create the options
directly on menu creation.
To do make tests more complete, we need to add some other callable!
>>> def option2():
... global option_selected
... option_selected = 1
>>> def option3():
... global option_selected
... option_selected = 1
>>> import sys
>>> def optionX(p):
... global option_selected
... option_selected = p
Now create a new menu instance:
>>> menu = KezMenu(
... ["First option!", option1],
... ["sEcond", option2],
... ["Again!", option3],
... ["Lambda", lambda: optionX(71)],
... ["Quit", sys.exit],
... )
You can fast create menu entries giving a list of couples that are our *label* and *callable* attributes.
The *__init__* method does the magic and save those values in the right way.
>>> type(menu.options[0]) == dict
True
>>> menu.options[1]['callable']
<function option2 at ...>
>>> [x['label'] for x in menu.options]
['First option!', 'sEcond', 'Again!', 'Lambda', 'Quit']
Like said above we will not use anymore the surface object, so we can simplify a little our dummy function:
>>> def drawMenu():
... screen.fill( (150,150,150,255) )
... menu.draw(screen)
... pygame.display.flip()
And now we refresh our window:
>>> drawMenu()
>>> waitForUserAction("All our options")
It's very important to see how the actions are linked to the menu items. We must create couples of labels and
*callable objects* without calling them! Like above you must pass the callable to the KezMenu a function,
you must not call the callable yourself.
We can also use as callable argument a method of an object, a Python standard callable (like the `sys.exit`__ above)
What if you wanna also need to pass parameter(s) to the callable? The use of the `lambda function`__ above will show you how
to do this!
__ http://docs.python.org/library/sys.html#sys.exit
__ http://docs.python.org/reference/expressions.html#id17
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.
__ http://www.pygame.org/docs/ref/event.html
>>> 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.
__ http://www.pygame.org/docs/ref/font.html#pygame.font.Font
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`__:
__ http://www.pygame.org/docs/ref/time.html#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
__ http://www.python.org/dev/peps/pep-0008/
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`__
__ https://github.com/keul/KezMenu