Skip to content

Commit

Permalink
Added all the code
Browse files Browse the repository at this point in the history
  • Loading branch information
Uglemat committed May 17, 2013
1 parent 137cbcb commit a55d57f
Show file tree
Hide file tree
Showing 8 changed files with 754 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
@@ -0,0 +1,2 @@
*.pyc
.backups
2 changes: 2 additions & 0 deletions kezmenu/__init__.py
@@ -0,0 +1,2 @@
from kezmenu import KezMenu, runTests
from kezmenu import __version__, __description__
175 changes: 175 additions & 0 deletions kezmenu/kezmenu.py
@@ -0,0 +1,175 @@
# -*- coding: utf-8 -*-

# KezMenu - By Luca Fabbri
# This code is released under GPL license
# ---------------------------------------------------------------
# This work is based on the original EzMeNu script, released from
# PyMike, from the Pygame community
# See http://www.pygame.org/project/855/
# ---------------------------------------------------------------

import pygame
import warnings

from kezmenu_effects import KezMenuEffectAble, VALID_EFFECTS

__author__ = "Keul - lucafbb AT gmail.com"
__version__ = "0.3.5"

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

class deprecated(object):
"""A decorator for deprecated functions"""

def __init__(self, msg):
self._msg = msg
self._printed = False

def __call__(self, func):
"""Log out the deprecation message, but only once"""
if not self._printed:
def wrapped_func(*args):
warnings.warn(self._msg % func.__name__, DeprecationWarning, stacklevel=3)
func(*args)
self._printed = True
return wrapped_func
return func

class KezMenu(KezMenuEffectAble):
"""A simple but complete class to handle menu using Pygame"""

def __init__(self, *options):
"""Initialise the EzMenu! options should be a sequence of lists in the
format of [option_name, option_function]
"""
KezMenuEffectAble.__init__(self)
self.options = [{'label': x[0], 'callable': x[1]} for x in options]
self.x = 0
self.y = 0
self.screen_topleft_offset = (0,0)
self.option = 0
self.width = 0
self.height = 0
self.color = (0, 0, 0, 0)
self.focus_color = (255, 0, 0, 255)
self.mouse_enabled = True
self.mouse_focus = False
# The 2 lines below seem stupid, but for effects I can need different font for every line.
try:
self._font = None
self.font = pygame.font.Font(None, 32)
self._fixSize()
except: # needed for fixing the common issues if the module is used in a py2exe app
pass

def _fixSize(self):
"""Fix the menu size. Commonly called when the font is changed"""
self.height = 0
for o in self.options:
text = o['label']
font = o['font']
ren = font.render(text, 1, (0, 0, 0))
if ren.get_width() > self.width:
self.width = ren.get_width()
self.height+=font.get_height()

def draw(self, surface):
"""Blit the menu to a surface."""
offset = 0
i = 0
ol, ot = self.screen_topleft_offset
first = self.options and self.options[0]
last = self.options and self.options[-1]
for o in self.options:
indent = o.get('padding_col',0)

# padding above the line
if o!=first and o.get('padding_line',0):
offset+=o['padding_line']

font = o.get('font',self._font)
if i==self.option and self.focus_color:
clr = self.focus_color
else:
clr = self.color
text = o['label']
ren = font.render(text, 1, clr)
if ren.get_width() > self.width:
self.width = ren.get_width()
o['label_rect'] = pygame.Rect( (ol+self.x + indent, ot+self.y + offset), (ren.get_width(),ren.get_height()) )
surface.blit(ren, (self.x + indent, self.y + offset))
offset+=font.get_height()

# padding below the line
if o!=last and o.get('padding_line',0):
offset+=o['padding_line']

i+=1

def update(self, events, time_passed=None):
"""Update the menu and get input for the menu.
@events: the pygame catched events
@time_passed: optional parameter, only used for animations. The time passed (in seconds) from the last
update call (commonly obtained from a call on pygame.Clock.tick)
"""
for e in events:
if e.type == pygame.KEYDOWN:
if e.key == pygame.K_DOWN:
self.option += 1
if e.key == pygame.K_UP:
self.option -= 1
if e.key == pygame.K_RETURN or e.key == pygame.K_SPACE:
self.options[self.option]['callable']()
# Mouse controls
elif e.type == pygame.MOUSEBUTTONDOWN:
lb, cb, rb = pygame.mouse.get_pressed()
if lb and self.mouse_focus:
self.options[self.option]['callable']()
# Menu limits
if self.option > len(self.options)-1:
self.option = len(self.options)-1
elif self.option < 0:
self.option = 0
# Check for mouse position
if self.mouse_enabled:
self._checkMousePositionForFocus()
if time_passed:
self._updateEffects(time_passed)

def _checkMousePositionForFocus(self):
"""Check the mouse position to know if move focus on a option"""
i = 0
mouse_pos = pygame.mouse.get_pos()
ml,mt = self.position
for o in self.options:
rect = o.get('label_rect')
if rect:
if rect.collidepoint(mouse_pos):
self.option = i
self.mouse_focus = True
break
i+=1
else:
self.mouse_focus = False

def _setPosition(self, position):
x,y = position
self.x = x
self.y = y
position = property(lambda self: (self.x,self.y), _setPosition, doc="""The menu position inside the container""")

def _setFont(self, font):
self._font = font
for o in self.options:
o['font'] = font
self._fixSize()
font = property(lambda self: self._font, _setFont, doc="""Font used by the menu""")

def center_at(self, x, y):
"""Center the menu at x,y"""
self.x = x-(self.width/2)
self.y = y-(self.height/2)


def runTests():
import tests
173 changes: 173 additions & 0 deletions kezmenu/kezmenu_effects.py
@@ -0,0 +1,173 @@
# -*- coding: utf-8 -*-

import pygame

VALID_EFFECTS = ('enlarge-font-on-focus','raise-line-padding-on-focus','raise-col-padding-on-focus')

class KezMenuEffectAble(object):
"""Base class used from KezMenu, to group all data and method needed for effects support"""

def __init__(self):
self._effects = {}

def enableEffect(self, name, **kwargs):
"""Enable an effect in the KezMEnu
Raise a KeyError if the name of the effect is not know.
Additional keyword argument will be passed to the propert effect's init method, and stored.
@name: the name of the effect as string (must be one of the kezmenu.VALID_EFFECTS values)
"""
if name not in VALID_EFFECTS:
raise KeyError("KezMenu don't know an effect of type %s" % name)
self.__getattribute__('_effectinit_%s' % name.replace("-","_"))(name, **kwargs)

def disableEffect(self, name):
"""Disable an effect"""
try:
del self._effects[name]
self.__getattribute__('_effectdisable_%s' % name.replace("-","_"))()
except KeyError:
pass
except AttributeError:
pass

def _updateEffects(self, time_passed):
"""Update method for the effects handle"""
for name,params in self._effects.items():
self.__getattribute__('_effectupdate_%s' % name.replace("-","_"))(time_passed)

# ******* Effects *******

def _effectinit_enlarge_font_on_focus(self, name, **kwargs):
"""Init the effect that enlarge the focused menu entry.
Keyword arguments can contain enlarge_time (seconds needed to raise the element size)
and enlarge_factor (a value that repr the size multiplier to be reached).
"""
self._effects[name] = kwargs
if not kwargs.has_key('font'):
raise TypeError("enlarge_font_on_focus: font parameter is required")
if not kwargs.has_key('size'):
raise TypeError("enlarge_font_on_focus: size parameter is required")
if not kwargs.has_key('enlarge_time'):
kwargs['enlarge_time'] = .5
if not kwargs.has_key('enlarge_factor'):
kwargs['enlarge_factor'] = 2.
kwargs['raise_font_ps'] = kwargs['enlarge_factor']/kwargs['enlarge_time'] # pixel-per-second
for o in self.options:
o['font'] = pygame.font.Font(kwargs['font'], kwargs['size'])
o['font_current_size'] = kwargs['size']
o['raise_font_factor'] = 1.

def _effectupdate_enlarge_font_on_focus(self, time_passed):
"""Gradually enlarge the font size of the focused line"""
data = self._effects['enlarge-font-on-focus']
fps = data['raise_font_ps']
i = 0
final_size = data['size'] * data['enlarge_factor']
for o in self.options:
if i==self.option:
# Raise me
if o['font_current_size']<final_size:
o['raise_font_factor']+=fps*time_passed
elif o['font_current_size']>final_size:
o['raise_font_factor']=data['enlarge_factor']
elif o['raise_font_factor']!=1.:
# decrease
if o['raise_font_factor']>1.:
o['raise_font_factor']-=fps*time_passed
elif o['raise_font_factor']<1.:
o['raise_font_factor'] = 1.

new_size = int(data['size'] * o['raise_font_factor'])
if new_size!=o['font_current_size']:
o['font'] = pygame.font.Font(data['font'], new_size)
o['font_current_size'] = new_size
i+=1

def _effectdisable_enlarge_font_on_focus(self):
"""Restore the base font"""
self.font = self._font


def _effectinit_raise_line_padding_on_focus(self, name, **kwargs):
"""Init the effect that raise the empty space above and below the focused entry.
Keyword arguments can contain enlarge_time (seconds needed to raise the element size)
and padding (a value that repr the number of pixel to be added above and below the focused line).
"""
self._effects[name] = kwargs
if not kwargs.has_key('enlarge_time'):
kwargs['enlarge_time'] = .5
if not kwargs.has_key('padding'):
kwargs['padding'] = 10
kwargs['padding_pps'] = kwargs['padding']/kwargs['enlarge_time'] # pixel-per-second
# Now, every menu voices need additional infos
for o in self.options:
o['padding_line']=0.

def _effectupdate_raise_line_padding_on_focus(self, time_passed):
"""Gradually enlarge the padding of the focused line.
If the focus move from a voice to another, also reduce padding of all other not focused entries.
"""
data = self._effects['raise-line-padding-on-focus']
pps = data['padding_pps']
i = 0
for o in self.options:
if i==self.option:
# Raise me
if o['padding_line']<data['padding']:
o['padding_line']+=pps*time_passed
elif o['padding_line']>data['padding']:
o['padding_line'] = data['padding']
elif o['padding_line']:
if o['padding_line']>0:
o['padding_line']-=pps*time_passed
elif o['padding_line']<0:
o['padding_line'] = 0
i+=1

def _effectdisable_raise_line_padding_on_focus(self):
"""Delete all line paddings"""
for o in self.options:
del o['padding_line']


def _effectinit_raise_col_padding_on_focus(self, name, **kwargs):
"""Init the effect that raise the empty space on the left of the focused entry.
Keyword arguments can contain enlarge_time (seconds needed to raise the element size)
and padding (a value that repr the number of pixel to be added above and below the focused line).
"""
self._effects[name] = kwargs
if not kwargs.has_key('enlarge_time'):
kwargs['enlarge_time'] = .5
if not kwargs.has_key('padding'):
kwargs['padding'] = 10
kwargs['padding_pps'] = kwargs['padding']/kwargs['enlarge_time'] # pixel-per-second
# Now, every menu voices need additional infos
for o in self.options:
o['padding_col']=0.

def _effectupdate_raise_col_padding_on_focus(self, time_passed):
"""Gradually enlarge the padding of the focused column.
If the focus move from a voice to another, also reduce padding of all other not focused entries.
"""
data = self._effects['raise-col-padding-on-focus']
pps = data['padding_pps']
i = 0
for o in self.options:
if i==self.option:
# Raise me
if o['padding_col']<data['padding']:
o['padding_col']+=pps*time_passed
elif o['padding_col']>data['padding']:
o['padding_col'] = data['padding']
elif o['padding_col']:
if o['padding_col']>0:
o['padding_col']-=pps*time_passed
elif o['padding_col']<0:
o['padding_col'] = 0
i+=1

def _effectdisable_raise_col_padding_on_focus(self):
"""Delete all column paddings"""
for o in self.options:
del o['padding_col']

5 changes: 5 additions & 0 deletions kezmenu/tests.py
@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-

import doctest

doctest.testfile("docs/README.txt", optionflags=doctest.ELLIPSIS|doctest.NORMALIZE_WHITESPACE)
5 changes: 5 additions & 0 deletions kezmenu/tests_effects.py
@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-

import doctest

doctest.testfile("docs/EFFECTS.txt", optionflags=doctest.ELLIPSIS|doctest.NORMALIZE_WHITESPACE)

0 comments on commit a55d57f

Please sign in to comment.