# HG changeset patch # User Stefano Rivera # Date 1328962186 -7200 # Node ID c0474fe18b96052d49aae5c4a33de81b885eaf7e # Parent 99a1420097df57e2040cf3a5866171d4aa627715 Copy in widgets from mamba (currently unused) diff -r 99a1420097df -r c0474fe18b96 pyntnclick/widgets.py --- a/pyntnclick/widgets.py Sat Feb 11 14:04:48 2012 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,215 +0,0 @@ -# widgets.py -# Copyright Boomslang team, 2010 (see COPYING File) - -"""Custom Albow widgets""" - -import textwrap - -import albow.controls -import albow.menu -from albow.resource import get_font, get_image -from pygame.color import Color -from pygame.rect import Rect -from pygame.draw import lines as draw_lines -from pygame import mouse - -from pyntnclick.constants import BUTTON_SIZE -from pyntnclick.cursor import CursorWidget - - -class BoomLabel(albow.controls.Label): - - trim_line_top = 0 - - def __init__(self, text, width=None, **kwds): - albow.controls.Label.__init__(self, text, width, **kwds) - w, h = self.size - h -= self.trim_line_top * len(self.text.split('\n')) - self.size = (w, h) - - def set_margin(self, margin): - """Add a set_margin method that recalculates the label size""" - old_margin = self.margin - w, h = self.size - d = margin - old_margin - self.margin = margin - self.size = (w + 2 * d, h + 2 * d) - - def draw_all(self, surface): - bg_color = self.bg_color - self.bg_color = None - if bg_color is not None: - new_surface = surface.convert_alpha() - new_surface.fill(bg_color) - surface.blit(new_surface, surface.get_rect()) - albow.controls.Label.draw_all(self, surface) - self._draw_all_no_bg(surface) - self.bg_color = bg_color - - def _draw_all_no_bg(self, surface): - pass - - def draw_with(self, surface, fg, _bg=None): - m = self.margin - align = self.align - width = surface.get_width() - y = m - lines = self.text.split("\n") - font = self.font - dy = font.get_linesize() - self.trim_line_top - for line in lines: - image = font.render(line, True, fg) - r = image.get_rect() - image = image.subsurface(r.clip(r.move(0, self.trim_line_top))) - r.top = y - if align == 'l': - r.left = m - elif align == 'r': - r.right = width - m - else: - r.centerx = width // 2 - surface.blit(image, r) - y += dy - - -class BoomButton(BoomLabel): - - def __init__(self, text, action, screen): - super(BoomButton, self).__init__(text, font=get_font(20, 'Vera.ttf'), - margin=4) - self.bg_color = (0, 0, 0) - self._frame_color = Color(50, 50, 50) - self.action = action - self.screen = screen - - def mouse_down(self, event): - self.action() - self.screen.state_widget.mouse_move(event) - - def mouse_move(self, event): - self.screen.state.highlight_override = True - - def draw(self, surface): - super(BoomButton, self).draw(surface) - r = surface.get_rect() - w = 2 - top, bottom, left, right = r.top, r.bottom, r.left, r.right - draw_lines(surface, self._frame_color, False, [ - (left, bottom), (left, top), (right - w, top), (right - w, bottom) - ], w) - - -class MessageDialog(BoomLabel, CursorWidget): - - def __init__(self, screen, text, wrap_width, style=None, **kwds): - CursorWidget.__init__(self, screen) - self.set_style(style) - paras = text.split("\n\n") - text = "\n".join([textwrap.fill(para, wrap_width) for para in paras]) - BoomLabel.__init__(self, text, **kwds) - - def set_style(self, style): - self.set_margin(5) - self.border_width = 1 - self.border_color = (0, 0, 0) - self.bg_color = (127, 127, 127) - self.fg_color = (0, 0, 0) - if style == "JIM": - self.set(font=get_font(20, "Monospace.ttf")) - self.trim_line_top = 10 - self.bg_color = Color(255, 175, 127, 191) - self.fg_color = (0, 0, 0) - self.border_color = (127, 15, 0) - - def draw_all(self, surface): - root_surface = self.get_root().surface - overlay = root_surface.convert_alpha() - overlay.fill(Color(0, 0, 0, 191)) - root_surface.blit(overlay, (0, 0)) - BoomLabel.draw_all(self, surface) - - def _draw_all_no_bg(self, surface): - CursorWidget.draw_all(self, surface) - - def mouse_down(self, event): - self.dismiss() - self.screen.state_widget._mouse_move(mouse.get_pos()) - for widget in self.screen.state_widget.subwidgets: - widget._mouse_move(mouse.get_pos()) - - def cursor_highlight(self): - return False - - -class HandButton(albow.controls.Image): - """The fancy hand button for the widget""" - - def __init__(self, action): - # FIXME: Yes, please. - this_image = get_image('items', 'hand.png') - albow.controls.Image.__init__(self, image=this_image) - self.action = action - self.set_rect(Rect(0, 0, BUTTON_SIZE, BUTTON_SIZE)) - - def mouse_down(self, event): - self.action() - - -class PopupMenuButton(albow.controls.Button): - - def __init__(self, text, action): - albow.controls.Button.__init__(self, text, action) - - self.font = get_font(16, 'Vera.ttf') - self.set_rect(Rect(0, 0, BUTTON_SIZE, BUTTON_SIZE)) - self.margin = (BUTTON_SIZE - self.font.get_linesize()) / 2 - - -class PopupMenu(albow.menu.Menu, CursorWidget): - - def __init__(self, screen): - CursorWidget.__init__(self, screen) - self.screen = screen - self.shell = screen.shell - items = [ - ('Quit Game', 'quit'), - ('Exit to Main Menu', 'main_menu'), - ] - # albow.menu.Menu ignores title string - albow.menu.Menu.__init__(self, None, items) - self.font = get_font(16, 'Vera.ttf') - - def show_menu(self): - """Call present, with the correct position""" - item_height = self.font.get_linesize() - menu_top = 600 - (len(self.items) * item_height + BUTTON_SIZE) - item = self.present(self.shell, (0, menu_top)) - if item > -1: - # A menu item needs to be invoked - self.invoke_item(item) - - -class BoomImageButton(albow.controls.Image): - """The fancy image button for the screens""" - - FOLDER = None - - def __init__(self, filename, x, y, action, enable=None): - this_image = get_image(self.FOLDER, filename) - albow.controls.Image.__init__(self, image=this_image) - self.action = action - self.set_rect(Rect((x, y), this_image.get_size())) - self.enable = enable - - def draw(self, surface): - if self.is_enabled(): - surface.blit(self.get_image(), self.get_rect()) - - def mouse_down(self, event): - if self.is_enabled(): - self.action() - - def is_enabled(self): - if self.enable: - return self.enable() - return True diff -r 99a1420097df -r c0474fe18b96 pyntnclick/widgets/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pyntnclick/widgets/__init__.py Sat Feb 11 14:09:46 2012 +0200 @@ -0,0 +1,217 @@ +# widgets.py +# Copyright Boomslang team, 2010 (see COPYING File) + +# XXX: This should be deleted when albow is gone + +"""Custom Albow widgets""" + +import textwrap + +import albow.controls +import albow.menu +from albow.resource import get_font, get_image +from pygame.color import Color +from pygame.rect import Rect +from pygame.draw import lines as draw_lines +from pygame import mouse + +from pyntnclick.constants import BUTTON_SIZE +from pyntnclick.cursor import CursorWidget + + +class BoomLabel(albow.controls.Label): + + trim_line_top = 0 + + def __init__(self, text, width=None, **kwds): + albow.controls.Label.__init__(self, text, width, **kwds) + w, h = self.size + h -= self.trim_line_top * len(self.text.split('\n')) + self.size = (w, h) + + def set_margin(self, margin): + """Add a set_margin method that recalculates the label size""" + old_margin = self.margin + w, h = self.size + d = margin - old_margin + self.margin = margin + self.size = (w + 2 * d, h + 2 * d) + + def draw_all(self, surface): + bg_color = self.bg_color + self.bg_color = None + if bg_color is not None: + new_surface = surface.convert_alpha() + new_surface.fill(bg_color) + surface.blit(new_surface, surface.get_rect()) + albow.controls.Label.draw_all(self, surface) + self._draw_all_no_bg(surface) + self.bg_color = bg_color + + def _draw_all_no_bg(self, surface): + pass + + def draw_with(self, surface, fg, _bg=None): + m = self.margin + align = self.align + width = surface.get_width() + y = m + lines = self.text.split("\n") + font = self.font + dy = font.get_linesize() - self.trim_line_top + for line in lines: + image = font.render(line, True, fg) + r = image.get_rect() + image = image.subsurface(r.clip(r.move(0, self.trim_line_top))) + r.top = y + if align == 'l': + r.left = m + elif align == 'r': + r.right = width - m + else: + r.centerx = width // 2 + surface.blit(image, r) + y += dy + + +class BoomButton(BoomLabel): + + def __init__(self, text, action, screen): + super(BoomButton, self).__init__(text, font=get_font(20, 'Vera.ttf'), + margin=4) + self.bg_color = (0, 0, 0) + self._frame_color = Color(50, 50, 50) + self.action = action + self.screen = screen + + def mouse_down(self, event): + self.action() + self.screen.state_widget.mouse_move(event) + + def mouse_move(self, event): + self.screen.state.highlight_override = True + + def draw(self, surface): + super(BoomButton, self).draw(surface) + r = surface.get_rect() + w = 2 + top, bottom, left, right = r.top, r.bottom, r.left, r.right + draw_lines(surface, self._frame_color, False, [ + (left, bottom), (left, top), (right - w, top), (right - w, bottom) + ], w) + + +class MessageDialog(BoomLabel, CursorWidget): + + def __init__(self, screen, text, wrap_width, style=None, **kwds): + CursorWidget.__init__(self, screen) + self.set_style(style) + paras = text.split("\n\n") + text = "\n".join([textwrap.fill(para, wrap_width) for para in paras]) + BoomLabel.__init__(self, text, **kwds) + + def set_style(self, style): + self.set_margin(5) + self.border_width = 1 + self.border_color = (0, 0, 0) + self.bg_color = (127, 127, 127) + self.fg_color = (0, 0, 0) + if style == "JIM": + self.set(font=get_font(20, "Monospace.ttf")) + self.trim_line_top = 10 + self.bg_color = Color(255, 175, 127, 191) + self.fg_color = (0, 0, 0) + self.border_color = (127, 15, 0) + + def draw_all(self, surface): + root_surface = self.get_root().surface + overlay = root_surface.convert_alpha() + overlay.fill(Color(0, 0, 0, 191)) + root_surface.blit(overlay, (0, 0)) + BoomLabel.draw_all(self, surface) + + def _draw_all_no_bg(self, surface): + CursorWidget.draw_all(self, surface) + + def mouse_down(self, event): + self.dismiss() + self.screen.state_widget._mouse_move(mouse.get_pos()) + for widget in self.screen.state_widget.subwidgets: + widget._mouse_move(mouse.get_pos()) + + def cursor_highlight(self): + return False + + +class HandButton(albow.controls.Image): + """The fancy hand button for the widget""" + + def __init__(self, action): + # FIXME: Yes, please. + this_image = get_image('items', 'hand.png') + albow.controls.Image.__init__(self, image=this_image) + self.action = action + self.set_rect(Rect(0, 0, BUTTON_SIZE, BUTTON_SIZE)) + + def mouse_down(self, event): + self.action() + + +class PopupMenuButton(albow.controls.Button): + + def __init__(self, text, action): + albow.controls.Button.__init__(self, text, action) + + self.font = get_font(16, 'Vera.ttf') + self.set_rect(Rect(0, 0, BUTTON_SIZE, BUTTON_SIZE)) + self.margin = (BUTTON_SIZE - self.font.get_linesize()) / 2 + + +class PopupMenu(albow.menu.Menu, CursorWidget): + + def __init__(self, screen): + CursorWidget.__init__(self, screen) + self.screen = screen + self.shell = screen.shell + items = [ + ('Quit Game', 'quit'), + ('Exit to Main Menu', 'main_menu'), + ] + # albow.menu.Menu ignores title string + albow.menu.Menu.__init__(self, None, items) + self.font = get_font(16, 'Vera.ttf') + + def show_menu(self): + """Call present, with the correct position""" + item_height = self.font.get_linesize() + menu_top = 600 - (len(self.items) * item_height + BUTTON_SIZE) + item = self.present(self.shell, (0, menu_top)) + if item > -1: + # A menu item needs to be invoked + self.invoke_item(item) + + +class BoomImageButton(albow.controls.Image): + """The fancy image button for the screens""" + + FOLDER = None + + def __init__(self, filename, x, y, action, enable=None): + this_image = get_image(self.FOLDER, filename) + albow.controls.Image.__init__(self, image=this_image) + self.action = action + self.set_rect(Rect((x, y), this_image.get_size())) + self.enable = enable + + def draw(self, surface): + if self.is_enabled(): + surface.blit(self.get_image(), self.get_rect()) + + def mouse_down(self, event): + if self.is_enabled(): + self.action() + + def is_enabled(self): + if self.enable: + return self.enable() + return True diff -r 99a1420097df -r c0474fe18b96 pyntnclick/widgets/base.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pyntnclick/widgets/base.py Sat Feb 11 14:09:46 2012 +0200 @@ -0,0 +1,310 @@ +import collections + +import pygame +from pygame.locals import (KEYDOWN, K_DOWN, K_LEFT, K_RETURN, K_RIGHT, K_UP, + K_KP_ENTER, MOUSEBUTTONDOWN, MOUSEBUTTONUP, + MOUSEMOTION, SRCALPHA, USEREVENT) + +from mamba.constants import UP, DOWN, LEFT, RIGHT +from mamba.engine import UserEvent + + +class Widget(object): + + def __init__(self, rect): + if not isinstance(rect, pygame.Rect): + rect = pygame.Rect(rect, (0, 0)) + self.rect = rect + self.focussable = False + self.focussed = False + self.modal = False + self.parent = None + self.disabled = False + self.callbacks = collections.defaultdict(list) + + def add_callback(self, eventtype, callback, *args): + self.callbacks[eventtype].append((callback, args)) + + def event(self, ev): + "Don't override this without damn good reason" + if self.disabled: + return True + + type_ = ev.type + if type_ == USEREVENT: + for k in self.callbacks.iterkeys(): + if (isinstance(k, type) and issubclass(k, UserEvent) + and k.matches(ev)): + type_ = k + break + + for callback, args in self.callbacks[type_]: + if callback(ev, self, *args): + return True + return False + + def draw(self, surface): + "Override me" + pass + + def grab_focus(self): + if self.focussable: + # Find the root and current focus + root = self + while root.parent is not None: + root = root.parent + focus = root + focus_modal_base = None + while (isinstance(focus, Container) + and focus.focussed_child is not None): + if focus.modal: + focus_modal_base = focus + focus = focus.children[focus.focussed_child] + + # Don't leave a modal heirarchy + if focus_modal_base: + widget = self + while widget.parent is not None: + if widget == focus_modal_base: + break + widget = widget.parent + else: + return False + + root.defocus() + widget = self + while widget.parent is not None: + parent = widget.parent + if isinstance(parent, Container): + idx = parent.children.index(widget) + parent.focussed_child = idx + widget = parent + self.focussed = True + return True + return False + + def disable(self): + if not self.disabled: + self.disabled = True + self._focussable_when_enabled = self.focussable + self.focussable = False + if hasattr(self, 'prepare'): + self.prepare() + + def enable(self): + if self.disabled: + self.disabled = False + self.focussable = self._focussable_when_enabled + if hasattr(self, 'prepare'): + self.prepare() + + +class Button(Widget): + + def event(self, ev): + if super(Button, self).event(ev): + return True + if (ev.type == MOUSEBUTTONDOWN + or (ev.type == KEYDOWN and ev.key in (K_RETURN, K_KP_ENTER))): + for callback, args in self.callbacks['clicked']: + if callback(ev, self, *args): + return True + return False + + def forced_click(self): + """Force calling the clicked handler""" + self.grab_focus() + for callback, args in self.callbacks['clicked']: + if callback(None, self, *args): + return True + return False + + +class Container(Widget): + + def __init__(self, rect=None): + if rect is None: + rect = pygame.Rect(0, 0, 0, 0) + super(Container, self).__init__(rect) + self.children = [] + self.focussed_child = None + + def event(self, ev): + """Push an event down through the tree, and fire our own event as a + last resort + """ + if ev.type in (MOUSEMOTION, MOUSEBUTTONUP, MOUSEBUTTONDOWN): + for child in self.children[:]: + if child.rect.collidepoint(ev.pos): + if ev.type == MOUSEBUTTONDOWN and child.focussable: + if not child.grab_focus(): + continue + if child.event(ev): + return True + + elif ev.type == KEYDOWN: + for i, child in enumerate(self.children): + if child.focussed or i == self.focussed_child: + if child.event(ev): + return True + else: + # Other events go to all children first + for child in self.children[:]: + if child.event(ev): + return True + if super(Container, self).event(ev): + return True + if (self.parent is None and ev.type == KEYDOWN + and ev.key in (K_UP, K_DOWN)): + return self.adjust_focus(1 if ev.key == K_DOWN else -1) + + def add(self, widget): + widget.parent = self + self.children.append(widget) + self.rect = self.rect.union(widget.rect) + + def remove(self, widget): + widget.parent = None + if self.focussed_child is not None: + child = self.children[self.focussed_child] + self.children.remove(widget) + # We don't update the rect, for reasons of simplificty and laziness + if self.focussed_child is not None and child in self.children: + # Fix focus index + self.focussed_child = self.children.index(child) + else: + self.focussed_child = None + + def defocus(self): + if self.focussed_child is not None: + child = self.children[self.focussed_child] + if isinstance(child, Container): + if not child.defocus(): + return False + child.focussed = False + self.focussed_child = None + return True + + def adjust_focus(self, direction): + """Try and adjust focus in direction (integer) + """ + if self.focussed_child is not None: + child = self.children[self.focussed_child] + if isinstance(child, Container): + if child.adjust_focus(direction): + return True + elif child.modal: + # We're modal, go back + if child.adjust_focus(-direction): + return True + else: + child.focussed = False + + current = self.focussed_child + if current is None: + current = -1 if direction > 0 else len(self.children) + if direction > 0: + possibles = list(enumerate(self.children))[current + 1:] + else: + possibles = list(enumerate(self.children))[:current] + possibles.reverse() + for i, child in possibles: + if child.focussable: + child.focussed = True + self.focussed_child = i + return True + if isinstance(child, Container): + if child.adjust_focus(direction): + self.focussed_child = i + return True + else: + if self.parent is None: + if self.focussed_child is not None: + # At the end, mark the last one as focussed, again + child = self.children[self.focussed_child] + if isinstance(child, Container): + if child.adjust_focus(-direction): + return True + else: + child.focussed = True + return True + else: + self.focussed_child = None + return False + + def draw(self, surface): + if self.parent is None and not self.focussed: + self.focussed = True + self.adjust_focus(1) + for child in self.children: + child.draw(surface) + + +class GridContainer(Container): + """Hacky container that only supports grids, won't work with Container + children, or modal children. + """ + + def __init__(self, width, rect=None): + super(GridContainer, self).__init__(rect) + self.width = width + + def event(self, ev): + if (ev.type == KEYDOWN and ev.key in (K_UP, K_DOWN, K_LEFT, K_RIGHT)): + direction = None + if ev.key == K_UP: + direction = UP + elif ev.key == K_DOWN: + direction = DOWN + elif ev.key == K_LEFT: + direction = LEFT + elif ev.key == K_RIGHT: + direction = RIGHT + return self.adjust_focus(direction) + super(GridContainer, self).event(ev) + + def add(self, widget): + assert not isinstance(widget, Container) + assert not widget.modal + super(GridContainer, self).add(widget) + + def adjust_focus(self, direction): + if isinstance(direction, int): + direction = (direction, 0) + + if len(self.children) == 0: + return False + + if self.focussed_child is None: + if sum(direction) > 0: + self.focussed_child = 0 + else: + self.focussed_child = len(self.children) - 1 + else: + self.children[self.focussed_child].focussed = False + if direction[0] != 0: + self.focussed_child += direction[0] + if direction[1] != 0: + self.focussed_child += self.width * direction[1] + if not 0 <= self.focussed_child < len(self.children): + self.focussed_child = None + return False + self.children[self.focussed_child].focussed = True + return True + + +class Box(Container): + """A container that draws a filled background with a border""" + padding = 4 + + def draw(self, surface): + expandrect = self.rect.move((-self.padding, -self.padding)) + expandrect.width = self.rect.width + 2 * self.padding + expandrect.height = self.rect.height + 2 * self.padding + border = pygame.Surface(expandrect.size, SRCALPHA) + border.fill(pygame.Color('black')) + surface.blit(border, expandrect) + background = pygame.Surface(self.rect.size, SRCALPHA) + background.fill(pygame.Color('gray')) + surface.blit(background, self.rect) + super(Box, self).draw(surface) diff -r 99a1420097df -r c0474fe18b96 pyntnclick/widgets/editlevel.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pyntnclick/widgets/editlevel.py Sat Feb 11 14:09:46 2012 +0200 @@ -0,0 +1,108 @@ +from mamba.data import get_tileset_list, get_track_list +from mamba.widgets.base import Box +from mamba.widgets.text import TextWidget, TextButton, EntryTextWidget +from mamba.widgets.listbox import ListBox + + +class EditLevelBox(Box): + """Edit details for a level map""" + + button_padding = 2 + + def __init__(self, rect, level, post_callback=None): + super(EditLevelBox, self).__init__(rect) + self.level = level + self.level_tileset = self.level.tileset.name + self.level_track = self.level.background_track + self.post_callback = post_callback + self.prepare() + self.modal = True + + def add_widget(self, cls, *args, **kw): + clicked = kw.pop('clicked', None) + offset = kw.pop('offset', (0, 0)) + pos = (self.widget_left + offset[0], + self.widget_top + offset[1]) + widget = cls(pos, *args, **kw) + if clicked: + widget.add_callback('clicked', *clicked) + self.add(widget) + self.widget_top += widget.rect.height + self.button_padding + return widget + + def prepare(self): + self.widget_left = self.rect.left + self.widget_top = self.rect.top + + self.add_widget(TextWidget, "Specify Level Details") + + self.filename = self.add_widget( + EntryTextWidget, self.level.level_name, prompt="File:") + + self.levelname = self.add_widget( + EntryTextWidget, self.level.name, prompt='Level Title:') + + self.authorname = self.add_widget( + EntryTextWidget, self.level.author, prompt='Author:') + + # self.tileset = self.add_widget( + # TextButton, 'Tileset: %s' % self.level_tileset, + # color='white', clicked=(self.list_tilesets,)) + + self.trackname = self.add_widget( + TextButton, 'Music: %s' % self.level_track, + color='white', clicked=(self.list_tracks,)) + + self.ok_button = self.add_widget( + TextButton, 'OK', offset=(10, 0), clicked=(self.close, True)) + + self.cancel_button = self.add_widget( + TextButton, 'Cancel', offset=(10, 0), clicked=(self.close, False)) + + self.rect.width = max(self.rect.width, 400) + self.rect.height += 5 + + def change_tileset(self, ev, widget, name): + self.level_tileset = name + self.tileset.text = 'Tileset: %s' % name + self.tileset.prepare() + + def change_track(self, ev, widget, name): + self.level_track = name + self.trackname.text = 'Music: %s' % name + self.trackname.prepare() + + def mk_loadlist(self, title, items, callback): + load_list = [] + for name in items: + load_button = TextButton((0, 0), name) + load_button.add_callback('clicked', callback, name) + load_list.append(load_button) + lb = ListBox((200, 200), title, load_list, 6) + lb.parent_modal = self.modal + self.modal = False + self.parent.add(lb) + lb.grab_focus() + + def list_tilesets(self, ev, widget): + tilesets = [i for i in get_tileset_list() if i != 'common'] + self.mk_loadlist('Select Tileset', tilesets, self.change_tileset) + + def list_tracks(self, ev, widget): + tracks = get_track_list() + self.mk_loadlist('Select Music', tracks, self.change_track) + + def close(self, ev, widget, do_update): + self.modal = False + self.parent.remove(self) + if do_update: + self.post_callback( + self.filename.value, + self.levelname.value, + self.authorname.value, + self.level_tileset, + self.level_track) + return True + + def grab_focus(self): + return self.ok_button.grab_focus() diff -r 99a1420097df -r c0474fe18b96 pyntnclick/widgets/editsprite.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pyntnclick/widgets/editsprite.py Sat Feb 11 14:09:46 2012 +0200 @@ -0,0 +1,148 @@ +from mamba.widgets.base import Box +from mamba.widgets.text import TextWidget, TextButton, EntryTextWidget + + +class EditSpriteBox(Box): + """Edit details for a special sprite on the level map""" + + def __init__(self, rect, sprite_pos, sprite_info, post_callback=None): + super(EditSpriteBox, self).__init__(rect) + self.sprite_pos = sprite_pos + sprite_cls_name, sprite_cls, sprite_id, args = sprite_info + self.sprite_cls_name = sprite_cls_name + self.sprite_cls = sprite_cls + if sprite_id: + self.sprite_id = sprite_id + else: + self.sprite_id = '' + self.sprite_parameters = args + self.new_sprite_parameters = [] + self.post_callback = post_callback + self.parameter_widgets = [] + self.prepare() + self.modal = True + + def prepare(self): + title = TextWidget(self.rect, "Specify Sprite Details") + self.add(title) + height = self.rect.top + title.rect.height + 2 + self.edit_sprite_name = TextWidget((self.rect.left, height), + 'Sprite Class: %s' % self.sprite_cls.name) + self.add(self.edit_sprite_name) + height += self.edit_sprite_name.rect.height + 2 + self.edit_sprite_id = EntryTextWidget((self.rect.left + 20, height), + self.sprite_id, prompt='Sprite Id (required):') + self.add(self.edit_sprite_id) + height += self.edit_sprite_id.rect.height + 2 + poss_params = self.sprite_cls.get_sprite_args() + if not poss_params: + self.sprite_param = TextWidget((self.rect.left, height), + 'No Parameters') + self.add(self.sprite_param) + height += self.sprite_param.rect.height + 2 + else: + self.sprite_param = TextWidget((self.rect.left, height), + 'Parameters') + self.add(self.sprite_param) + height += self.sprite_param.rect.height + 2 + for i, param_tuple in enumerate(poss_params): + if len(self.sprite_parameters) > i: + value = self.sprite_parameters[i] + else: + value = None + if param_tuple[1] is None: + # Text Entry Parameter + if value is None: + value = '' + edit_widget = EntryTextWidget( + (self.rect.left + 20, height), + value, prompt=param_tuple[0]) + self.parameter_widgets.append(edit_widget) + self.add(edit_widget) + height += edit_widget.rect.height + elif isinstance(param_tuple[1], tuple): + # We have a list of possible values + if value is None: + value = param_tuple[1][0] # Take the first + mylist = [] + list_width = 0 + list_height = 0 + for choice in param_tuple[1]: + list_parameter = TextWidget( + (self.rect.left + 20, height), + '%s: %s' % (param_tuple[0], choice)) + # So we can pull it out of this later + list_parameter.choice = choice + list_width = max(list_width, list_parameter.rect.width) + change_list = TextButton( + (list_parameter.rect.right + 5, height), + 'Next Option') + change_list.add_callback('clicked', self.change_list, + choice, param_tuple[1], mylist) + mylist.append((list_parameter, change_list)) + list_height = max(list_height, change_list.rect.height, + list_parameter.rect.height) + if choice == value: + self.add(list_parameter) + self.add(change_list) + for x in mylist: + x[1].rect.left = self.rect.left + list_width + 25 + if x[0].rect.height < list_height: + x[0].rect.top += (list_height - + x[0].rect.height) / 2 + height += max(list_parameter.rect.height, + change_list.rect.height) + self.parameter_widgets.append(mylist) + # FIXME: Other cases + height += 20 + self.ok_button = TextButton((self.rect.left + 10, height), 'OK') + self.ok_button.add_callback('clicked', self.close, True) + self.add(self.ok_button) + cancel_button = TextButton((self.ok_button.rect.right + 10, height), + 'Cancel') + cancel_button.add_callback('clicked', self.close, False) + self.add(cancel_button) + self.rect.width = max(self.rect.width, 400) + self.rect.height += 5 + + def change_list(self, ev, widget, cur_choice, all_choices, widget_list): + pos = all_choices.index(cur_choice) + if pos == len(all_choices) - 1: + next_pos = 0 + else: + next_pos = pos + 1 + self.remove(widget_list[pos][0]) + self.remove(widget_list[pos][1]) + self.add(widget_list[next_pos][0]) + self.add(widget_list[next_pos][1]) + + def close(self, ev, widget, do_update): + if do_update: + self.new_sprite_parameters = [] + for param in self.parameter_widgets: + if hasattr(param, 'value'): + self.new_sprite_parameters.append(param.value) + elif isinstance(param, list): + # Find the selected one + for choice, _ in param: + if choice in self.children: + # Is selected, so we grab this choice + self.new_sprite_parameters.append(choice.choice) + break + self.sprite_id = self.edit_sprite_id.value + sprite = self.make_sprite() + if not self.post_callback(sprite): + return # Call-back failed, so don't remove + self.parent.paused = False + self.parent.remove(self) + return True + + def make_sprite(self): + """Convert values to a sprite line""" + pos = "%s, %s" % self.sprite_pos + sprite_string = "%s: %s %s %s" % (pos, self.sprite_cls_name, + self.sprite_id, " ".join(self.new_sprite_parameters)) + return sprite_string + + def grab_focus(self): + return self.ok_button.grab_focus() diff -r 99a1420097df -r c0474fe18b96 pyntnclick/widgets/entrybox.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pyntnclick/widgets/entrybox.py Sat Feb 11 14:09:46 2012 +0200 @@ -0,0 +1,66 @@ +from pygame.constants import K_ESCAPE, K_RETURN, K_KP_ENTER, KEYDOWN + +from mamba.widgets.base import Box +from mamba.widgets.text import TextWidget, TextButton, EntryTextWidget + + +class EntryBox(Box): + + def __init__(self, rect, text, init_value, accept_callback=None, + color='white', entry_color='red'): + super(EntryBox, self).__init__(rect) + self.text = text + self.accept_callback = accept_callback + self.color = color + self.entry_color = entry_color + self.value = init_value + self.prepare() + self.modal = True + + def prepare(self): + message = TextWidget((self.rect.left + 50, self.rect.top + 2), + self.text, color=self.color) + self.rect.width = message.rect.width + 100 + self.add(message) + self.entry_text = EntryTextWidget((self.rect.left + 5, + self.rect.top + message.rect.height + 5), self.value, + focus_color=self.entry_color) + self.add_callback(KEYDOWN, self.edit) + self.add(self.entry_text) + ok_button = TextButton((self.rect.left + 50, + self.entry_text.rect.bottom), 'Accept') + ok_button.add_callback('clicked', self.close, True) + self.add(ok_button) + cancel_button = ok_button = TextButton( + (ok_button.rect.right + 10, self.entry_text.rect.bottom), + 'Cancel') + cancel_button.add_callback('clicked', self.close, False) + self.add(cancel_button) + self.rect.height += 5 + + def close(self, ev, widget, ok): + if self.accept_callback and ok: + self.value = self.entry_text.value + if self.accept_callback(self.value): + if self.parent: + if hasattr(self.parent, 'paused'): + self.parent.paused = False + self.parent.remove(self) + # Don't remove if the accept callback failed + return + if hasattr(self.parent, 'paused'): + self.parent.paused = False + self.parent.remove(self) + return True + + def edit(self, ev, widget): + if ev.key == K_ESCAPE: + self.close(ev, widget, False) + return True + elif ev.key in (K_RETURN, K_KP_ENTER): + self.close(ev, widget, True) + return True + return False # pass this up to parent + + def grab_focus(self): + self.entry_text.grab_focus() diff -r 99a1420097df -r c0474fe18b96 pyntnclick/widgets/game.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pyntnclick/widgets/game.py Sat Feb 11 14:09:46 2012 +0200 @@ -0,0 +1,50 @@ +"""Display the game area.""" + +from pygame.rect import Rect +from pygame.locals import (KEYDOWN, K_LEFT, K_RIGHT, K_DOWN, K_UP, K_p, + K_SPACE, K_PAUSE) + +from mamba.constants import UP, DOWN, LEFT, RIGHT +from mamba.widgets.base import Widget +from mamba.engine import FlipArrowsEvent + + +class GameWidget(Widget): + def __init__(self, world, offset=(0, 0)): + self.world = world + self.actions = self.create_action_map() + rect = Rect(offset, world.get_size()) + super(GameWidget, self).__init__(rect) + self.focussable = True + self.add_callback(KEYDOWN, self.action_callback) + self.add_callback(FlipArrowsEvent, self.flip_arrows) + + def create_action_map(self): + actions = {} + pause = (self.world.toggle_pause, ()) + actions[K_LEFT] = (self.world.snake.send_new_direction, (LEFT,)) + actions[K_RIGHT] = (self.world.snake.send_new_direction, (RIGHT,)) + actions[K_DOWN] = (self.world.snake.send_new_direction, (DOWN,)) + actions[K_UP] = (self.world.snake.send_new_direction, (UP,)) + actions[K_p] = pause + actions[K_SPACE] = pause + actions[K_PAUSE] = pause + return actions + + def action_callback(self, ev, widget): + if ev.key in self.actions: + func, args = self.actions[ev.key] + func(*args) + return True + + def flip_arrows(self, ev, widget): + self.world.level.flip_arrows() + + def draw(self, surface): + self.world.update() + self.world.draw(surface) + + def restart(self): + self.world.restart() + self.actions = self.create_action_map() + self.grab_focus() diff -r 99a1420097df -r c0474fe18b96 pyntnclick/widgets/imagebutton.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pyntnclick/widgets/imagebutton.py Sat Feb 11 14:09:46 2012 +0200 @@ -0,0 +1,44 @@ +import pygame +from pygame.locals import SRCALPHA + +from mamba.constants import COLOR, FONT_SIZE, FOCUS_COLOR +from mamba.widgets.base import Button +from mamba.widgets.text import TextWidget + + +class ImageButtonWidget(Button, TextWidget): + """Text label with image on the left""" + + def __init__(self, rect, image, text, fontsize=FONT_SIZE, color=COLOR): + self.image = image + self.focus_color = pygame.Color(FOCUS_COLOR) + self.padding = 5 + self.border = 3 + super(ImageButtonWidget, self).__init__(rect, text, fontsize, color) + self.focussable = True + + def prepare(self): + super(ImageButtonWidget, self).prepare() + text_surface = self.surface + # Image is already a surface + self._focussed = self.focussed + color = self.focus_color if self.focussed else self.color + + width = (text_surface.get_width() + self.image.get_width() + + 5 + self.padding * 2) + height = max(text_surface.get_height(), + self.image.get_height()) + self.padding * 2 + self.rect.width = max(self.rect.width, width) + self.rect.height = max(self.rect.height, height) + self.surface = pygame.Surface(self.rect.size, SRCALPHA) + self.surface.fill(0) + self.surface.blit(self.image, (self.padding, self.padding)) + self.surface.blit(text_surface, + (self.image.get_width() + 5 + self.padding, self.padding)) + pygame.draw.rect(self.surface, color, self.surface.get_rect(), + self.border) + + def draw(self, surface): + if self._focussed != self.focussed: + self.prepare() + super(ImageButtonWidget, self).draw(surface) diff -r 99a1420097df -r c0474fe18b96 pyntnclick/widgets/level.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pyntnclick/widgets/level.py Sat Feb 11 14:09:46 2012 +0200 @@ -0,0 +1,144 @@ +from pygame.rect import Rect +from pygame.locals import MOUSEBUTTONDOWN, MOUSEBUTTONUP, MOUSEMOTION +from pygame.key import set_repeat + +from mamba.widgets.base import Widget +from mamba.constants import TILE_SIZE +from mamba.snake import Snake +from mamba.engine import FlipArrowsEvent, ReplayEvent + + +class EditLevelWidget(Widget): + def __init__(self, level, offset=(0, 0)): + self.level = level + level_rect = Rect(offset, level.get_size()) + self.main_tool = None + self.tool = None + self.drawing = False + self.last_click_pos = None + self.tile_mode = True # Flag for sprite interactions + super(EditLevelWidget, self).__init__(level_rect) + self.add_callback(MOUSEBUTTONDOWN, self.place_tile) + self.add_callback(MOUSEBUTTONUP, self.end_draw) + self.add_callback(MOUSEMOTION, self.draw_tiles) + self.add_callback(FlipArrowsEvent, self.flip_arrows) + self.add_callback(ReplayEvent, self.handle_replay) + self.snake = None + self.snake_alive = False + self.replay_data = [] + self.last_run = [] + self.replay_pos = 0 + + def get_replay(self): + return self.replay_data[:] + + def replay(self, run=None): + if run is None: + # We exclude the last couple of steps, so we don't redo + # the final crash in this run + run = self.last_run[:-4] + if len(run) > 0: + self.start_test() + ReplayEvent.post(run, 0) + + def handle_replay(self, ev, widget): + self.apply_action(ev.run[ev.replay_pos]) + if ev.replay_pos < len(ev.run) - 1: + ReplayEvent.post(ev.run, ev.replay_pos + 1) + + def start_test(self): + self.level.update_tiles_ascii() + self.last_run = self.replay_data + self.replay_data = [] + self.level.restart() + tile_pos, orientation = self.level.get_entry() + self.snake = Snake(tile_pos, orientation) + set_repeat(40, 100) + self.snake_alive = True + + def stop_test(self): + self.snake = None + self.snake_alive = False + self.level.restart() + set_repeat(0, 0) + + def draw(self, surface): + self.level.draw(surface) + if self.snake: + self.snake.draw(surface) + + def kill_snake(self): + """Prevent user interaction while snake is dead""" + self.snake_alive = False + + def restart(self): + self.start_test() + + def interact(self, segment): + if not self.snake or not self.snake_alive: + return + tiles = segment.filter_collisions(self.level.sprites) + for tile in tiles: + tile.interact(self, segment) + + def get_sprite(self, sprite_id): + return self.level.extra_sprites[sprite_id] + + def apply_action(self, orientation): + self.replay_data.append(orientation) + if not self.snake or not self.snake_alive: + return + # We choose numbers that are close to, but not exactly, move 0.5 tiles + # to avoid a couple of rounding corner cases in the snake code + if orientation is None or orientation == self.snake.orientation: + self.snake.update(9.99 / self.snake.speed, self) + else: + self.snake.send_new_direction(orientation) + self.snake.update(9.99 / self.snake.speed, self) + + def set_tool(self, new_tool): + self.main_tool = new_tool + self.tool = new_tool + + def end_draw(self, event, widget): + self.drawing = False + + def draw_tiles(self, event, widget): + if self.drawing and self.tool: + # FIXME: Need to consider leaving and re-entering the widget + self.update_tile(self.convert_pos(event.pos)) + + def flip_arrows(self, ev, widget): + self.level.flip_arrows() + + def place_tile(self, event, widget): + if self.tile_mode: + if event.button == 1: # Left button + self.tool = self.main_tool + else: + self.tool = '.' + self.drawing = True + if self.tool: + self.update_tile(self.convert_pos(event.pos)) + else: + self.last_click_pos = self.convert_pos(event.pos) + self.drawing = False + + def convert_pos(self, pos): + return (pos[0] / TILE_SIZE[0], pos[1] / TILE_SIZE[1]) + + def update_tile(self, tile_pos): + """Update the tile at the current mouse position""" + if self.check_paused(): + return # Do nothing if dialogs showing + # We convert our current position into a tile position + # and replace the tile with the current tool + old_tile = self.level.get_tile(tile_pos) + if self.tool == '.' and old_tile is None: + return + elif old_tile is not None and old_tile.tile_char == self.tool: + return + self.level.replace_tile(tile_pos, self.tool) + + def check_paused(self): + return hasattr(self.parent, 'paused') and self.parent.paused diff -r 99a1420097df -r c0474fe18b96 pyntnclick/widgets/levelbutton.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pyntnclick/widgets/levelbutton.py Sat Feb 11 14:09:46 2012 +0200 @@ -0,0 +1,86 @@ +import pygame +from pygame.locals import SRCALPHA + +from mamba.constants import FOCUS_COLOR +from mamba.data import load_image +from mamba.widgets.base import Button +from mamba.widgets.text import TextWidget + + +class LevelButton(Button): + + def __init__(self, rect, level, done=False): + super(LevelButton, self).__init__(rect) + self.level = level + self.text = level.name + self.done = done + self.focussable = True + self.border = 2 + self.rect.width = 100 + self.rect.height = 120 + self.prepare() + + def make_thumbnail(self, dest_rect): + level_surface = pygame.Surface(self.level.get_size(), SRCALPHA) + self.level.draw(level_surface) + size = level_surface.get_rect().fit(dest_rect).size + level_thumbnail = pygame.transform.scale(level_surface, size) + return level_thumbnail + + def prepare(self): + self.surface = pygame.Surface(self.rect.size, SRCALPHA) + self.surface.fill(0) + + dest_rect = pygame.Rect((self.border, self.border), + (self.rect.width - self.border, + self.rect.height - self.border)) + if not hasattr(self.level, 'button_thumbnail'): + self.level.button_thumbnail = self.make_thumbnail(dest_rect) + self.surface.blit(self.level.button_thumbnail, dest_rect) + + if self.done: + image = load_image('menus/tick.png') + self.surface.blit(image, image.get_rect()) + + # We only have space for two lines + self._text_lines = self.wrap_text(self.text)[:2] + + text_height = sum(line.rect.height for line in self._text_lines) + text_pos = self.level.button_thumbnail.get_rect().height + text_pos += (self.rect.height - text_height - text_pos) // 2 + for line in self._text_lines: + text_rect = pygame.Rect(((self.rect.width - line.rect.width) // 2, + text_pos), + line.rect.size) + text_pos = text_rect.bottom + self.surface.blit(line.surface, text_rect) + + color = pygame.Color(FOCUS_COLOR if self.focussed else '#444444') + pygame.draw.rect(self.surface, color, self.surface.get_rect(), + self.border + 1) + self._state = (self.done, self.focussed) + + def wrap_text(self, text): + args = {'rect': (0, 0), + 'text': text, + 'fontsize': 12, + 'color': 'white', + } + w = TextWidget(**args) + w.prepare() + splitpoint = len(text) + max_width = self.rect.width - (self.border * 3) + while w.rect.width > max_width and ' ' in text[:splitpoint]: + splitpoint = text.rfind(' ', 0, splitpoint) + args['text'] = text[:splitpoint] + w = TextWidget(**args) + w.prepare() + if splitpoint < len(text): + return [w] + self.wrap_text(text[splitpoint + 1:]) + else: + return [w] + + def draw(self, surface): + if self._state != (self.done, self.focussed): + self.prepare() + surface.blit(self.surface, self.rect) diff -r 99a1420097df -r c0474fe18b96 pyntnclick/widgets/listbox.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pyntnclick/widgets/listbox.py Sat Feb 11 14:09:46 2012 +0200 @@ -0,0 +1,45 @@ +from mamba.widgets.base import Box +from mamba.widgets.toollist import ToolListWidget +from mamba.widgets.text import TextWidget, TextButton + + +class ListBox(Box): + + def __init__(self, rect, text, widget_list, page_length=8): + super(ListBox, self).__init__(rect) + self.message = TextWidget(rect, text) + self.toolbar = ToolListWidget(rect, widget_list, page_length, + start_key=None) + self.prepare() + self.modal = True + + def prepare(self): + width = max(self.toolbar.rect.width, self.message.rect.width) + if width > self.message.rect.width: + message_pos = (self.rect.left + width / 2 + - self.message.rect.width / 2, self.rect.top) + else: + message_pos = (self.rect.left, self.rect.top + 5) + tool_pos = (self.rect.left, + self.rect.top + self.message.rect.height + 2) + self.message.rect.topleft = message_pos + self.toolbar.rect.topleft = tool_pos + self.toolbar.fill_page() # Fix alignment + self.add(self.message) + self.add(self.toolbar) + self.ok_button = ok_button = TextButton((0, 0), 'OK') + ok_pos = (self.rect.left + width / 2 - ok_button.rect.width / 2, + tool_pos[1] + 2 + self.toolbar.rect.height) + ok_button.rect.topleft = ok_pos + ok_button.add_callback('clicked', self.close) + self.add(ok_button) + self.rect.height += 5 + + def close(self, ev, widget): + if hasattr(self.parent, 'paused'): + self.parent.paused = False + self.parent.remove(self) + return True + + def grab_focus(self): + return self.ok_button.grab_focus() diff -r 99a1420097df -r c0474fe18b96 pyntnclick/widgets/messagebox.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pyntnclick/widgets/messagebox.py Sat Feb 11 14:09:46 2012 +0200 @@ -0,0 +1,54 @@ +from mamba.constants import FONT_SIZE +from mamba.widgets.base import Box +from mamba.widgets.text import TextWidget, TextButton + + +class MessageBox(Box): + + def __init__(self, rect, text, post_callback=None, color='red', + fontsize=FONT_SIZE): + super(MessageBox, self).__init__(rect) + self.text = text + self.font_size = fontsize + self.post_callback = post_callback + self.color = color + self.prepare() + self.modal = True + + def prepare(self): + cont = TextWidget((0, 0), "Press [OK] or Enter to continue", + fontsize=self.font_size) + widgets = [] + width = cont.rect.width + for line in self.text.split('\n'): + message = TextWidget((0, 0), line, color=self.color, + fontsize=self.font_size) + widgets.append(message) + width = max(width, message.rect.width) + widgets.append(cont) + top = self.rect.top + 10 + left = self.rect.left + 5 + for widget in widgets: + pos = (left + width / 2 - widget.rect.width / 2, top) + widget.rect.topleft = pos + top += widget.rect.height + 5 + self.add(widget) + self.ok_button = ok_button = TextButton((0, 0), 'OK') + ok_pos = (self.rect.left + 5 + width / 2 - ok_button.rect.width / 2, + top + 5) + ok_button.rect.topleft = ok_pos + ok_button.add_callback('clicked', self.close) + self.add(ok_button) + self.rect.height += 5 + + def close(self, ev, widget): + if hasattr(self.parent, 'paused'): + self.parent.paused = False + self.parent.remove(self) + if self.post_callback: + self.post_callback() + if getattr(self, 'parent_modal', False): + self.parent.modal = True + + def grab_focus(self): + return self.ok_button.grab_focus() diff -r 99a1420097df -r c0474fe18b96 pyntnclick/widgets/overlay.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pyntnclick/widgets/overlay.py Sat Feb 11 14:09:46 2012 +0200 @@ -0,0 +1,29 @@ +from mamba.widgets.base import Button + + +class OverlayOnFocusButton(Button): + """A non-visiable clickable area, that causes an overlay to be + displayed when focussed""" + + def __init__(self, rect, image): + self.image = image + super(OverlayOnFocusButton, self).__init__(rect) + self.focussable = True + + def draw(self, surface): + if self.focussed: + surface.blit(self.image, surface.get_rect()) + + +class OverlayButton(Button): + """A non-visiable clickable area, that causes an overlay to be + displayed. Doesn't really understand this focus thing.""" + + def __init__(self, rect, image): + self.image = image + super(OverlayButton, self).__init__(rect) + self.focussable = True + + def draw(self, surface): + if not self.disabled: + surface.blit(self.image, surface.get_rect()) diff -r 99a1420097df -r c0474fe18b96 pyntnclick/widgets/text.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pyntnclick/widgets/text.py Sat Feb 11 14:09:46 2012 +0200 @@ -0,0 +1,131 @@ +import pygame +from pygame.constants import (SRCALPHA, KEYDOWN, K_ESCAPE, K_RETURN, K_UP, + K_DOWN, K_SPACE, K_KP_ENTER) + +from mamba.constants import COLOR, FONT_SIZE, FOCUS_COLOR, DELETE_KEYS +from mamba.widgets.base import Widget, Button +from mamba.data import filepath +from mamba.constants import DEFAULT_FONT + + +class TextWidget(Widget): + fontcache = {} + + def __init__(self, rect, text, fontsize=FONT_SIZE, color=COLOR): + super(TextWidget, self).__init__(rect) + self.text = text + self.fontsize = fontsize + self.color = color + self.prepare() + + def prepare(self): + self.fontname = DEFAULT_FONT + font = (self.fontname, self.fontsize) + if font not in TextWidget.fontcache: + fontfn = filepath('fonts/' + self.fontname) + TextWidget.fontcache[font] = pygame.font.Font(fontfn, + self.fontsize) + self.font = TextWidget.fontcache[font] + if not isinstance(self.color, pygame.Color): + self.color = pygame.Color(self.color) + self.surface = self.font.render(self.text, True, self.color) + self.text_rect = self.surface.get_rect() + width, height = self.surface.get_rect().size + self.rect.width = max(self.rect.width, width) + self.rect.height = max(self.rect.height, height) + + def draw(self, surface): + surface.blit(self.surface, self.rect) + + +class TextButton(Button, TextWidget): + def __init__(self, *args, **kwargs): + self.focus_color = kwargs.pop('focus_color', FOCUS_COLOR) + self.padding = kwargs.pop('padding', 10) + self.border = kwargs.pop('border', 3) + super(TextButton, self).__init__(*args, **kwargs) + if not isinstance(self.focus_color, pygame.Color): + self.focus_color = pygame.Color(self.focus_color) + self.focussable = True + + def prepare(self): + super(TextButton, self).prepare() + text = self.surface + text_rect = self.text_rect + self._focussed = self.focussed + if self.disabled: + color = pygame.Color('#666666') + elif self.focussed: + color = self.focus_color + else: + color = self.color + + width = text_rect.width + self.padding * 2 + height = text_rect.height + self.padding * 2 + self.rect.width = max(self.rect.width, width) + self.rect.height = max(self.rect.height, height) + self.surface = pygame.Surface(self.rect.size, SRCALPHA) + self.surface.fill(0) + self.surface.blit(text, text.get_rect().move(self.padding, + self.padding)) + pygame.draw.rect(self.surface, color, self.surface.get_rect(), + self.border) + + def draw(self, surface): + if self._focussed != self.focussed: + self.prepare() + super(TextButton, self).draw(surface) + + def event(self, ev): + if ev.type == KEYDOWN and ev.key == K_SPACE: + return self.forced_click() + return super(TextButton, self).event(ev) + + +class EntryTextWidget(TextWidget): + def __init__(self, rect, text, **kwargs): + self.focus_color = kwargs.pop('focus_color', FOCUS_COLOR) + self.prompt = kwargs.pop('prompt', 'Entry:') + self.value = text + text = '%s %s' % (self.prompt, text) + self.base_color = COLOR + self.update_func = kwargs.pop('update', None) + super(EntryTextWidget, self).__init__(rect, text, **kwargs) + if not isinstance(self.focus_color, pygame.Color): + self.focus_color = pygame.Color(self.focus_color) + self.focussable = True + self.base_color = self.color + self.add_callback(KEYDOWN, self.update) + + def update(self, ev, widget): + old_value = self.value + if ev.key in DELETE_KEYS: + if self.value: + self.value = self.value[:-1] + elif ev.key in (K_ESCAPE, K_RETURN, K_KP_ENTER, K_UP, K_DOWN): + return False # ignore these + else: + self.value += ev.unicode + if old_value != self.value: + self.text = '%s %s' % (self.prompt, self.value) + self.prepare() + return True + + def prepare(self): + self.color = self.focus_color if self.focussed else self.base_color + super(EntryTextWidget, self).prepare() + self._focussed = self.focussed + + def draw(self, surface): + if self._focussed != self.focussed: + self.prepare() + super(EntryTextWidget, self).draw(surface) + if self.focussed: + text_rect = self.text_rect + # Warning, 00:30 AM code on the last Saturday + cur_start = text_rect.move( + (self.rect.left + 2, self.rect.top + 3)).topright + cur_end = text_rect.move( + (self.rect.left + 2, self.rect.top - 3)).bottomright + pygame.draw.line(surface, self.focus_color, + cur_start, cur_end, 2) diff -r 99a1420097df -r c0474fe18b96 pyntnclick/widgets/toollist.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pyntnclick/widgets/toollist.py Sat Feb 11 14:09:46 2012 +0200 @@ -0,0 +1,85 @@ +from pygame.constants import KEYUP, K_1, K_PAGEDOWN, K_PAGEUP + +from mamba.widgets.base import Container +from mamba.widgets.text import TextButton + + +class ToolListWidget(Container): + """List of other widgets, with some paging trickery""" + + def __init__(self, rect, widget_list, page_length, start_key=K_1, + padding=2): + widget_list.sort(key=lambda w: w.text) + self.widget_list = widget_list + self.page_length = page_length + self.padding = padding + self.page = 0 + self.start_key = start_key + super(ToolListWidget, self).__init__(rect) + self.prev_but = None + self.next_but = None + self.fill_page() + # We do this to avoid needing to worry about focus too much + self.add_callback(KEYUP, self.handle_key) + self.focussable = True + + def fill_page(self): + for widget in self.children[:]: + self.remove(widget) + self.hot_keys = {} + start_page = self.page * self.page_length + end_page = start_page + self.page_length + button_height = self.rect.top + self.padding + button_left = self.rect.left + self.padding + key = self.start_key + for widget in self.widget_list[start_page:end_page]: + widget.rect.topleft = (button_left, button_height) + self.add(widget) + if key: + self.hot_keys[key] = widget + key += 1 + button_height += widget.rect.height + self.padding + if not self.prev_but: + self.prev_but = TextButton((button_left, button_height), + u'\N{LEFTWARDS ARROW}') + self.prev_but.add_callback('clicked', self.change_page, -1) + else: + self.prev_but.rect.top = max(button_height, self.prev_but.rect.top) + if not self.next_but: + self.next_but = TextButton((button_left + 100, button_height), + u'\N{RIGHTWARDS ARROW}') + self.next_but.add_callback('clicked', self.change_page, 1) + else: + self.next_but.rect.top = max(button_height, self.next_but.rect.top) + if start_page > 0: + self.prev_but.enable() + else: + self.prev_but.disable() + if end_page < len(self.widget_list): + self.next_but.enable() + else: + self.next_but.disable() + self.add(self.prev_but) + self.add(self.next_but) + for widget in self.children[:]: + if widget in self.widget_list: + # Standardise widdths + widget.rect.width = self.rect.width - 2 + widget.prepare() + + def handle_key(self, ev, widget): + if hasattr(self.parent, 'paused') and self.parent.paused: + # No hotjets when pasued + return False + if ev.key in self.hot_keys: + widget = self.hot_keys[ev.key] + return widget.forced_click() + elif ev.key == K_PAGEDOWN and self.prev_but: + return self.prev_but.forced_click() + elif ev.key == K_PAGEUP and self.next_but: + return self.next_but.forced_click() + + def change_page(self, ev, widget, change): + self.page += change + self.fill_page() + return True