/menu.py
https://bitbucket.org/hyperion/simplemenu · Python · 224 lines · 150 code · 16 blank · 58 comment · 22 complexity · b1f32ced18e876cff398ea0eea14761f MD5 · raw file
- #! /usr/bin/python
- # -*- coding: utf-8 -*-
- import pygame
- from codecs import open
- import json
- def import_string(import_name):
- """
- Inspired by a function from the infamous werkzeug framework.
- the name is given by a dotted notation as shown here:
- - modul.funktion
- or:
- - modul.funktion:parameter
- or:
- - modul.modul.modul.funktion:parameter
- :param import_name: name of the function in dotted style
- :return: imported object
- """
- param = None
- try:
- module, obj = import_name.rsplit('.', 1)
- if ":" in obj:
- obj, param = obj.split(":", 1)
- return getattr(__import__(module, None, None, [obj]), obj), param
- except (ImportError, AttributeError):
- raise
- class Menu(object):
- """
- this represents a menu object.
- The core idea is to generate a separate surface for each given option
- and possible state.
- The states are selected or not selected, depending on the itemindex.
- Later depending on the actual state a item can be drawn in the
- approriate color just by its state.
- """
- def __init__(self, displayname, items, key, font, default_color,
- selected_color, itemindex=0, itemheight=30, menuwidth=150):
- self.displayname = displayname,
- self.items = self.generate_menu_surfaces(items, font,
- default_color, selected_color)
- self.key = key
- self.itemindex = itemindex
- self.itemheight = itemheight
- self.menuwidth = menuwidth
- def __repr__(self):
- return "Menu({0})".format(self.displayname)
- def generate_menu_surfaces(self, options, font, default_color,
- selected_color):
- items = []
- for option in options:
- item = {
- False: font.render(option["name"], True, default_color),
- True: font.render(option["name"], True, selected_color),
- "action": option["action"],
- # eigentlich unnötig, aber so zur Not noch einmal Textausgabe
- # möglich
- "text": option["name"],
- }
- items.append(item)
- return items
- def move_down(self):
- self.itemindex += 1
- if self.itemindex == len(self.items):
- self.itemindex = 0
- def move_up(self):
- self.itemindex -= 1
- if self.itemindex < 0:
- self.itemindex = len(self.items)-1
- def load_menu(fname, menuname=None):
- """
- Parses all Menus (or one special) out of a JSON file and generates
- a dictioary of Menu objects from that data.
- It has the following format:
- {
- "default_color": [R, G , B],
- "selected_color": [R, G, B],
- "fontname": string,
- "menus": {
- "main": {
- "display": string,
- "itemheight": int,
- "menuwidth": int,
- "itemindex": int,
- "key": string,
- "items": [
- {
- "name": string,
- "action": "module.function:parameter"
- },
- ...
- ]
- },
- "another_menu": {
- ...
- }
- }
- }
- Also a key_binding map ist built by the given keys for each menu. It maps
- one key-string to a Menu object, so one can use that later on to fire up
- the approriate menu for a hitten key.
- If a menuname is given, it only generates the Menu object for that special
- menu. No key_binding will be generated as the user seems to handle all by
- himself.
- :return: dict of Menus, dict of key_bindings
- """
- menus = {}
- key_binding = {}
- with open(fname, "r", encoding="utf-8") as infile:
- rawdata = json.load(infile)
- default_color = rawdata.get("default_color", (255, 255, 255))
- selected_color = rawdata.get("selected_color", (255, 0, 0))
- font = pygame.font.Font(rawdata.get("fontname"),
- rawdata.get("fontsize", 30)
- )
- if menuname:
- menunames = [menuname]
- else:
- menunames = rawdata["menus"].keys()
- try:
- for menuid in menunames:
- menu_data = rawdata["menus"][menuid]
- menus[menuid] = Menu(
- displayname = menu_data["display"],
- items = menu_data["items"],
- key = menu_data["key"],
- font = font,
- default_color = default_color,
- selected_color = selected_color,
- itemindex = menu_data.get("itemindex", 0),
- itemheight = menu_data.get("itemsize", 30),
- menuwidth = menu_data.get("menuwidth", 150)
- )
- key_binding[menu_data["key"]] = menus[menuid]
- except KeyError, e:
- print "Menü '{0}' existiert nicht in der Datei '{1}'".format(
- menuname, fname
- )
- return
- else:
- return menus[menuname] if menuname else menus, key_binding
- def render_menu(menu, surface, position):
- """
- Paints a given Menu object onto a given surface at a given position.
- :return: a rect of the painted area (usefull to make a backup of that)
- """
- x, y = position
- for index, item in enumerate(menu.items):
- surface.blit(
- item[menu.itemindex==index],
- (x, y + (index*menu.itemheight))
- )
- return pygame.Rect(x, y, menu.menuwidth, len(menu.items)*menu.itemheight)
- def restore_screen(backup_surface, screen):
- """
- Redraw the original screen after the menu was closed.
- """
- screen.blit(backup_surface, (0, 0))
- pygame.display.update(backup_surface.get_rect())
- def menu_loop(screen, menu):
- """
- a simple endless looping function to take control over user input as long
- as no menuitem was chosen (ENTER) or the user decided to quit (ESCAPE).
- One can use KEY_UP and KEY_DOWN to navigate though the menu.
- :return: the chosen menuitem, the action-function object,
- a given parameter
- TODO: make left-upper corner variable (now it is (100, 100))
- """
- menu_action = {
- pygame.K_DOWN: menu.move_down,
- pygame.K_UP: menu.move_up
- }
- # keep the actual screen in a copy
- backup_surface = screen.copy()
- menu_rect = render_menu(menu, screen, (100, 100))
- pygame.display.update(menu_rect)
- while True:
- for event in pygame.event.get():
- if event.type == pygame.KEYDOWN:
- if event.key == pygame.K_ESCAPE:
- # mit 'Escape'-Taste raus
- restore_screen(backup_surface, screen)
- return None, None, None
- elif event.key in (pygame.K_UP, pygame.K_DOWN):
- # hier auf- und abbewegen der Auswahl
- menu_action[event.key]()
- menu_rect = render_menu(menu, screen, (100, 100))
- pygame.display.update(menu_rect)
- elif event.key == pygame.K_RETURN:
- # Item & Action hinter einem Menüpunkt zurückgeben
- action, param = import_string(
- menu.items[menu.itemindex]["action"]
- )
- restore_screen(backup_surface, screen)
- return menu.items[menu.itemindex], action, param