Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
754 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
*.pyc | ||
.backups |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
from kezmenu import KezMenu, runTests | ||
from kezmenu import __version__, __description__ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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'] | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
import doctest | ||
|
||
doctest.testfile("docs/README.txt", optionflags=doctest.ELLIPSIS|doctest.NORMALIZE_WHITESPACE) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
import doctest | ||
|
||
doctest.testfile("docs/EFFECTS.txt", optionflags=doctest.ELLIPSIS|doctest.NORMALIZE_WHITESPACE) |
Oops, something went wrong.