# HG changeset patch # User Stefano Rivera # Date 1403381169 -7200 # Node ID 79b5c1be9a5ed526eb5673b66b383ccea361128d # Parent f95830b58336d86493925eff6710db0dfe60d741 Remove pyntnclick, it's its own library, now diff -r f95830b58336 -r 79b5c1be9a5e gamelib/version.py --- a/gamelib/version.py Sat Jun 21 22:04:35 2014 +0200 +++ b/gamelib/version.py Sat Jun 21 22:06:09 2014 +0200 @@ -72,6 +72,7 @@ ] INSTALL_REQUIRES = [ + 'pyntnclick', ] # Install these manually diff -r f95830b58336 -r 79b5c1be9a5e pyntnclick/__init__.py diff -r f95830b58336 -r 79b5c1be9a5e pyntnclick/constants.py --- a/pyntnclick/constants.py Sat Jun 21 22:04:35 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,46 +0,0 @@ -# Useful constants -# copyright boomslang team (see COPYRIGHT file for details) - -import os - -DEBUG_ENVVAR = 'PYNTNCLICK_DEBUG' - - -def _get_debug(): - debug = os.getenv(DEBUG_ENVVAR, default=False) - if debug in [False, 'False', '0']: - return False - return True - - -class GameConstants(object): - title = None - short_name = 'pyntnclick' - # Icon for the main window, in the icons basedir - icon = None - - screen = (800, 600) - snd_freq = 44100 - snd_bitsize = -16 - snd_channels = 2 - snd_buffer = 1024 # no. of samples - - button_size = 50 - scene_size = (screen[0], screen[1] - button_size) - frame_rate = 25 - debug = _get_debug() - - font = 'DejaVuSans.ttf' - bold_font = 'DejaVuSans-Bold.ttf' - mono_font = 'DejaVuSans-Mono.ttf' - font_size = 16 - text_color = 'black' - label_padding = 10 - label_border = 3 - label_bg_color = (180, 180, 180, 220) - label_border_color = (0, 0, 0, 0xFF) - button_color = (0xFF, 0xFF, 0xFF, 0xFF) - button_bg_color = (0x66, 0x66, 0x66, 0xFF) - button_disabled_color = (0x66, 0x66, 0x66, 0xFF) - - modal_obscure_color = (0, 0, 0, 0xB0) diff -r f95830b58336 -r 79b5c1be9a5e pyntnclick/cursor.py --- a/pyntnclick/cursor.py Sat Jun 21 22:04:35 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,85 +0,0 @@ -# cursor.py -# Copyright Boomslang team, 2010 (see COPYING File) -# Sprite Cursor - -from pygame.sprite import Sprite, RenderUpdates -import pygame -import pygame.color -import pygame.mouse - -from pyntnclick.engine import Screen -from pyntnclick.image_transforms import Colour - - -class CursorSprite(Sprite): - "A Sprite that follows the Cursor" - - def __init__(self, filename, x=None, y=None): - Sprite.__init__(self) - self.filename = filename - self.pointer_x = x - self.pointer_y = y - self.highlighted = False - self.highlight_colour = (255, 100, 100, 255) - - def load(self, resources): - if not hasattr(self, 'plain_image'): - self.highlight_transform = Colour(self.highlight_colour) - self.plain_image = resources.get_image('items', self.filename) - self.highlighted_image = resources.get_image('items', - self.filename, transforms=(self.highlight_transform,)) - self.image = self.plain_image - self.rect = self.image.get_rect() - if self.pointer_x is None: - self.pointer_x = self.rect.size[0] // 2 - if self.pointer_y is None: - self.pointer_y = self.rect.size[1] // 2 - - def update(self): - pos = pygame.mouse.get_pos() - self.rect.left = pos[0] - self.pointer_x - self.rect.top = pos[1] - self.pointer_y - - def set_highlight(self, enable): - self.image = self.highlighted_image if enable else self.plain_image - - -HAND = CursorSprite('hand.png', 12, 0) - - -class CursorScreen(Screen): - """A Screen with custom cursors""" - - def setup(self): - self._cursor_group = RenderUpdates() - self._loaded_cursor = None - self.set_cursor(None) - - def on_enter(self): - super(CursorScreen, self).on_enter() - pygame.mouse.set_visible(0) - - def on_exit(self): - super(CursorScreen, self).on_exit() - pygame.mouse.set_visible(1) - - def draw(self, surface): - super(CursorScreen, self).draw(surface) - self.set_cursor(self.game.tool) - self._loaded_cursor.set_highlight(self.cursor_highlight()) - self._cursor_group.update() - self._cursor_group.draw(surface) - - def set_cursor(self, item): - if item is None or item.CURSOR is None: - cursor = HAND - else: - cursor = item.CURSOR - if cursor != self._loaded_cursor: - self._loaded_cursor = cursor - self._loaded_cursor.load(self.gd.resource) - self._cursor_group.empty() - self._cursor_group.add(self._loaded_cursor) - - def cursor_highlight(self): - return self.container.mouseover_widget.highlight_cursor diff -r f95830b58336 -r 79b5c1be9a5e pyntnclick/data/__init__.py diff -r f95830b58336 -r 79b5c1be9a5e pyntnclick/data/images/pyntnclick/end.png Binary file pyntnclick/data/images/pyntnclick/end.png has changed diff -r f95830b58336 -r 79b5c1be9a5e pyntnclick/data/images/pyntnclick/hand.png Binary file pyntnclick/data/images/pyntnclick/hand.png has changed diff -r f95830b58336 -r 79b5c1be9a5e pyntnclick/data/images/pyntnclick/start.png Binary file pyntnclick/data/images/pyntnclick/start.png has changed diff -r f95830b58336 -r 79b5c1be9a5e pyntnclick/data/locale/README.txt --- a/pyntnclick/data/locale/README.txt Sat Jun 21 22:04:35 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3 +0,0 @@ -This is for mo files for the pyntnclick tools. - -We don't want to store the mo files in VCS. diff -r f95830b58336 -r 79b5c1be9a5e pyntnclick/data/po/POTFILES --- a/pyntnclick/data/po/POTFILES Sat Jun 21 22:04:35 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -pyntnclick/tools/rect_drawer.py diff -r f95830b58336 -r 79b5c1be9a5e pyntnclick/data/po/af.po --- a/pyntnclick/data/po/af.po Sat Jun 21 22:04:35 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,154 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR , YEAR. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2013-01-29 17:45+0200\n" -"PO-Revision-Date: 2013-01-27 14:11+0200\n" -"Last-Translator: Neil Muller \n" -"Language-Team: Afrikaans\n" -"Language: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" - -#: pyntnclick/tools/rect_drawer.py:127 -msgid "Close" -msgstr "Sluit" - -#: pyntnclick/tools/rect_drawer.py:161 -msgid "draw" -msgstr "teken" - -#: pyntnclick/tools/rect_drawer.py:163 -msgid "cycle" -msgstr "" - -#: pyntnclick/tools/rect_drawer.py:165 -msgid "delete" -msgstr "verwyder" - -#: pyntnclick/tools/rect_drawer.py:167 -msgid "image" -msgstr "prent" - -#: pyntnclick/tools/rect_drawer.py:173 -#, python-format -msgid "Thing %(thing)s Interact %(interact)s" -msgstr "Ding %(thing)s Interact %(interact)s" - -#: pyntnclick/tools/rect_drawer.py:178 -msgid " Rects" -msgstr " Reghoeke" - -#: pyntnclick/tools/rect_drawer.py:203 -msgid "Existing Intersecting rects" -msgstr "Bestande reghoeke wat krius" - -#: pyntnclick/tools/rect_drawer.py:225 pyntnclick/tools/rect_drawer.py:239 -msgid "Intersecting rects" -msgstr "Reghoeke wat kruis" - -#: pyntnclick/tools/rect_drawer.py:226 -#, python-format -msgid " Object %s" -msgstr " Voorwerp %s" - -#: pyntnclick/tools/rect_drawer.py:240 -#, python-format -msgid " Object %(object1)s and %(object2)s" -msgstr " Voorwerpe %(object1)s en %(object2)s" - -#: pyntnclick/tools/rect_drawer.py:365 -#, python-format -msgid "Rect %d : " -msgstr "Reghoek %d : " - -#: pyntnclick/tools/rect_drawer.py:371 -#, python-format -msgid "Image %d" -msgstr "Prentjie %d" - -#: pyntnclick/tools/rect_drawer.py:593 -msgid "Mode : " -msgstr "Modus : " - -#: pyntnclick/tools/rect_drawer.py:600 -#, python-format -msgid "Mode : %s" -msgstr "Modus : %s" - -#: pyntnclick/tools/rect_drawer.py:630 -#, python-format -msgid "Invalid scene: %s" -msgstr "Ongelidge toneel: %s" - -#: pyntnclick/tools/rect_drawer.py:637 -#, python-format -msgid "Invalid detail: %s" -msgstr "Ongelidge gedeelte toneel: %s" - -#: pyntnclick/tools/rect_drawer.py:646 -msgid "Draw Rect" -msgstr "Teken Reghoek" - -#: pyntnclick/tools/rect_drawer.py:649 -msgid "Load image" -msgstr "Laai prentjie" - -#: pyntnclick/tools/rect_drawer.py:652 -msgid "Place/Move images" -msgstr "Plass/Beweeg prente" - -#: pyntnclick/tools/rect_drawer.py:658 -msgid "Cycle interacts" -msgstr "" - -#: pyntnclick/tools/rect_drawer.py:661 -msgid "Delete Objects" -msgstr "Verwyder Voorwerpe" - -#: pyntnclick/tools/rect_drawer.py:667 -msgid "Print objects" -msgstr "" - -#: pyntnclick/tools/rect_drawer.py:671 -msgid "Show Things (t)" -msgstr "Wys Dinge (t)" - -#: pyntnclick/tools/rect_drawer.py:675 -msgid "Show Thing Rects (r)" -msgstr "Wys Ding Reghoeke (r)" - -#: pyntnclick/tools/rect_drawer.py:679 -msgid "Show Images (i)" -msgstr "Wys Prentjies (i)" - -#: pyntnclick/tools/rect_drawer.py:683 -msgid "Opaque Images (o)" -msgstr "" - -#: pyntnclick/tools/rect_drawer.py:687 -msgid "Show Drawn Rects (d)" -msgstr "" - -#: pyntnclick/tools/rect_drawer.py:691 -msgid "Show Toolbar (b)" -msgstr "" - -#: pyntnclick/tools/rect_drawer.py:695 -msgid "Show Animations (a)" -msgstr "" - -#: pyntnclick/tools/rect_drawer.py:699 -msgid "Zoom (z)" -msgstr "" - -#: pyntnclick/tools/rect_drawer.py:703 -msgid "Quit" -msgstr "" diff -r f95830b58336 -r 79b5c1be9a5e pyntnclick/data/po/pyntnclick-tools.pot --- a/pyntnclick/data/po/pyntnclick-tools.pot Sat Jun 21 22:04:35 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,154 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR , YEAR. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2013-01-30 17:33+0200\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"Language: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=CHARSET\n" -"Content-Transfer-Encoding: 8bit\n" - -#: pyntnclick/tools/rect_drawer.py:127 -msgid "Close" -msgstr "" - -#: pyntnclick/tools/rect_drawer.py:161 -msgid "draw" -msgstr "" - -#: pyntnclick/tools/rect_drawer.py:163 -msgid "cycle" -msgstr "" - -#: pyntnclick/tools/rect_drawer.py:165 -msgid "delete" -msgstr "" - -#: pyntnclick/tools/rect_drawer.py:167 -msgid "image" -msgstr "" - -#: pyntnclick/tools/rect_drawer.py:173 -#, python-format -msgid "Thing %(thing)s Interact %(interact)s" -msgstr "" - -#: pyntnclick/tools/rect_drawer.py:178 -msgid " Rects" -msgstr "" - -#: pyntnclick/tools/rect_drawer.py:203 -msgid "Existing Intersecting rects" -msgstr "" - -#: pyntnclick/tools/rect_drawer.py:225 pyntnclick/tools/rect_drawer.py:239 -msgid "Intersecting rects" -msgstr "" - -#: pyntnclick/tools/rect_drawer.py:226 -#, python-format -msgid " Object %s" -msgstr "" - -#: pyntnclick/tools/rect_drawer.py:240 -#, python-format -msgid " Object %(object1)s and %(object2)s" -msgstr "" - -#: pyntnclick/tools/rect_drawer.py:365 -#, python-format -msgid "Rect %d : " -msgstr "" - -#: pyntnclick/tools/rect_drawer.py:371 -#, python-format -msgid "Image %d" -msgstr "" - -#: pyntnclick/tools/rect_drawer.py:593 -msgid "Mode : " -msgstr "" - -#: pyntnclick/tools/rect_drawer.py:600 -#, python-format -msgid "Mode : %s" -msgstr "" - -#: pyntnclick/tools/rect_drawer.py:630 -#, python-format -msgid "Invalid scene: %s" -msgstr "" - -#: pyntnclick/tools/rect_drawer.py:637 -#, python-format -msgid "Invalid detail: %s" -msgstr "" - -#: pyntnclick/tools/rect_drawer.py:646 -msgid "Draw Rect" -msgstr "" - -#: pyntnclick/tools/rect_drawer.py:649 -msgid "Load image" -msgstr "" - -#: pyntnclick/tools/rect_drawer.py:652 -msgid "Place/Move images" -msgstr "" - -#: pyntnclick/tools/rect_drawer.py:658 -msgid "Cycle interacts" -msgstr "" - -#: pyntnclick/tools/rect_drawer.py:661 -msgid "Delete Objects" -msgstr "" - -#: pyntnclick/tools/rect_drawer.py:667 -msgid "Print objects" -msgstr "" - -#: pyntnclick/tools/rect_drawer.py:671 -msgid "Show Things (t)" -msgstr "" - -#: pyntnclick/tools/rect_drawer.py:675 -msgid "Show Thing Rects (r)" -msgstr "" - -#: pyntnclick/tools/rect_drawer.py:679 -msgid "Show Images (i)" -msgstr "" - -#: pyntnclick/tools/rect_drawer.py:683 -msgid "Opaque Images (o)" -msgstr "" - -#: pyntnclick/tools/rect_drawer.py:687 -msgid "Show Drawn Rects (d)" -msgstr "" - -#: pyntnclick/tools/rect_drawer.py:691 -msgid "Show Toolbar (b)" -msgstr "" - -#: pyntnclick/tools/rect_drawer.py:695 -msgid "Show Animations (a)" -msgstr "" - -#: pyntnclick/tools/rect_drawer.py:699 -msgid "Zoom (z)" -msgstr "" - -#: pyntnclick/tools/rect_drawer.py:703 -msgid "Quit" -msgstr "" diff -r f95830b58336 -r 79b5c1be9a5e pyntnclick/data/sources/images/end.xcf Binary file pyntnclick/data/sources/images/end.xcf has changed diff -r f95830b58336 -r 79b5c1be9a5e pyntnclick/data/sources/images/start.xcf Binary file pyntnclick/data/sources/images/start.xcf has changed diff -r f95830b58336 -r 79b5c1be9a5e pyntnclick/engine.py --- a/pyntnclick/engine.py Sat Jun 21 22:04:35 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,155 +0,0 @@ -"""Game engine and top-level game loop.""" - -import pygame -import pygame.event -import pygame.display -import pygame.time -from pygame.locals import QUIT, USEREVENT - -# We can't do this via our usual UserEvent trickey -# as it gets generated by pygame.music, which only -# takes an event type -MUSIC_ENDED = USEREVENT + 1 - - -class Engine(object): - def __init__(self, gd): - self._screen = None - self._gd = gd - self.screens = {} - - def set_screen(self, screen_name): - if self._screen is not None: - self._screen.on_exit() - self._screen = self.screens[screen_name] - if self._screen is not None: - self._screen.on_enter() - - def add_screen(self, name, screen): - self.screens[name] = screen - - def run(self): - """Game loop.""" - - get_events = pygame.event.get - flip = pygame.display.flip - clock = pygame.time.Clock() - while True: - events = get_events() - for ev in events: - if ev.type == QUIT: - return - elif ev.type == MUSIC_ENDED: - self._gd.sound.music_ended() - elif ScreenChangeEvent.matches(ev): - self.set_screen(ev.screen_name) - elif ScreenEvent.matches(ev): - self.screens[ev.screen_name].process_event(ev.event_name, - ev.data) - else: - self._screen.dispatch(ev) - # Ping the screen / scene - self._screen.animate() - surface = pygame.display.get_surface() - self._screen.draw(surface) - flip() - self._fps = 1000.0 / clock.tick( - self._gd.constants.frame_rate) - - -class Screen(object): - """A top level object for the screen being displayed""" - - def __init__(self, gd): - # Avoid import loop - from pyntnclick.widgets.base import Container - - self.gd = gd - self.resource = gd.resource - - self.surface_size = gd.constants.screen - self.surface = None - self.container = Container((0, 0), self.gd, self.surface_size) - self.setup() - - def on_enter(self): - """Called when this becomes the current screen.""" - # Create the surface here as flipping between editor and - # other things kills pygame.display - self.surface = pygame.Surface(self.surface_size) - - def on_exit(self): - """Called when this stops being the current screen.""" - self.surface = None - - def setup(self): - """Override for initialization""" - pass - - def dispatch(self, ev): - self.container.event(ev) - - def animate(self): - """Called every tick - used for peroidic events, etc. - - Interested classes are expected to override this""" - pass - - def draw_background(self): - self.surface.fill(pygame.Color('gray')) - - def draw(self, surface): - if self.surface: - self.draw_background() - self.container.draw(self.surface) - surface.blit(self.surface, self.surface.get_rect()) - - def display_dialog(self, dialog): - self.container.paused = True - self.container.add(dialog) - dialog.grab_focus() - - def change_screen(self, new_screen_name): - ScreenChangeEvent.post(new_screen_name) - - def screen_event(self, screen_name, event_name, data=None): - ScreenEvent.post(screen_name, event_name, data) - - def process_event(self, event_name, data): - pass - - -class UserEvent(object): - """A user event type allowing subclassing, - to provide an infinate number of user-defined events - """ - - TYPE = "UNKNOWN" - - @classmethod - def post(cls, **kws): - ev = pygame.event.Event(USEREVENT, utype=cls.TYPE, **kws) - pygame.event.post(ev) - - @classmethod - def matches(cls, ev): - return ev.type == USEREVENT and ev.utype == cls.TYPE - - -class ScreenChangeEvent(UserEvent): - - TYPE = "SCREEN_CHANGE" - - @classmethod - def post(cls, screen_name): - super(ScreenChangeEvent, cls).post(screen_name=screen_name) - - -class ScreenEvent(UserEvent): - - TYPE = "SCREEN_EVENT" - - @classmethod - def post(cls, screen_name, event_name, data): - super(ScreenEvent, cls).post(screen_name=screen_name, - event_name=event_name, data=data) diff -r f95830b58336 -r 79b5c1be9a5e pyntnclick/gamescreen.py --- a/pyntnclick/gamescreen.py Sat Jun 21 22:04:35 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,399 +0,0 @@ -# gamescreen.py -# Copyright Boomslang team, 2010 (see COPYING File) -# Main menu for the game - -import pygame.draw -from pygame import Surface -from pygame.color import Color -from pygame.locals import MOUSEBUTTONDOWN, MOUSEMOTION, KEYDOWN, K_ESCAPE - -from pyntnclick.i18n import _ -from pyntnclick.cursor import CursorScreen -from pyntnclick.engine import Screen -from pyntnclick.widgets.base import ( - Container, ModalStackContainer, ModalWrapper) -from pyntnclick.widgets.text import TextButton, WrappedTextLabel -from pyntnclick.widgets.imagebutton import ImageButtonWidget - - -class InventorySlot(ImageButtonWidget): - SELECTED_COLOR = Color("yellow") - SELECTED_WIDTH = 2 - - def __init__(self, pos, gd, size): - self.item = None - super(InventorySlot, self).__init__(pos, gd, None, size) - self.add_callback(MOUSEBUTTONDOWN, self.mouse_down) - - def set_item(self, item): - self.item = item - - def draw(self, surface): - if self.item: - surface.blit(self.item.get_inventory_image(), self.rect) - if self.selected: - pygame.draw.rect(surface, self.SELECTED_COLOR, - self.rect, self.SELECTED_WIDTH) - - @property - def selected(self): - return self.parent.game.tool is self.item - - def mouse_down(self, event, widget): - if event.button != 1 or not self.item: - return - if self.selected: - self.parent.select(None) - elif self.item.is_interactive(self.parent.game.tool): - result = self.item.interact(self.parent.game.tool) - self.parent.screen.handle_result(result) - else: - self.parent.select(self.item) - - -class UpDownButton(TextButton): - # TextButton for now. - def __init__(self, pos, gd, size=None): - super(UpDownButton, self).__init__(pos, gd, self.TEXT, size=size, - padding=3) - - -class UpButton(UpDownButton): - TEXT = u'\N{UPWARDS ARROW}UP' - - -class DownButton(UpDownButton): - TEXT = u'\N{DOWNWARDS ARROW}DN' - - -class InventoryView(Container): - MIN_UPDOWN_WIDTH = 20 - - def __init__(self, pos, gd, size, screen): - self.bsize = gd.constants.button_size - super(InventoryView, self).__init__(pos, gd, size) - self.screen = screen - self.game = screen.game - - slots = (self.rect.width - self.MIN_UPDOWN_WIDTH) / self.bsize - self.slots = [self.add(self.make_slot(i)) for i in range(slots)] - self.inv_offset = 0 - - self.updown_width = self.rect.width - slots * self.bsize - ud_left = self.rect.right - self.updown_width - self.up_button = self.add(UpButton((ud_left, self.rect.top), gd, - (self.updown_width, self.rect.height / 2))) - self.up_button.add_callback(MOUSEBUTTONDOWN, self.up_callback) - self.down_button = self.add(DownButton( - (ud_left, self.rect.top + self.rect.height / 2), gd, - (self.updown_width, self.rect.height / 2))) - self.down_button.add_callback(MOUSEBUTTONDOWN, self.down_callback) - - self.add_callback(MOUSEBUTTONDOWN, self.mouse_down) - self.update_slots() - - def make_slot(self, slot): - pos = (self.rect.left + slot * self.bsize, self.rect.top) - size = (self.bsize, self.rect.height) - return InventorySlot(pos, self.gd, size) - - def up_callback(self, event, widget): - self.inv_offset = max(self.inv_offset - len(self.slots), 0) - self.update_slots() - - def down_callback(self, event, widget): - self.inv_offset += len(self.slots) - self.update_slots() - - def update_slots(self): - items = (self.slot_items + [None] * len(self.slots))[:len(self.slots)] - for item, slot in zip(items, self.slots): - slot.set_item(item) - - if self.inv_offset <= 0: - self.up_button.disable() - else: - self.up_button.enable() - - max_slot = (self.inv_offset + len(self.slots)) - if max_slot >= len(self.game.inventory()): - self.down_button.disable() - else: - self.down_button.enable() - - @property - def slot_items(self): - item_names = self.game.inventory()[self.inv_offset:][:len(self.slots)] - return [self.game.get_item(name) for name in item_names] - - def mouse_down(self, event, widget): - if event.button != 1: - self.game.set_tool(None) - - def select(self, tool): - self.game.set_tool(tool) - - -class SceneWidget(Container): - DETAIL_BORDER = 4 - DETAIL_BORDER_COLOR = Color("black") - - def __init__(self, pos, gd, size, scene, screen, is_detail=False): - super(SceneWidget, self).__init__(pos, gd, size) - self.name = scene.NAME - self.scene = scene - self.screen = screen - self.game = screen.game - self.add_callback(MOUSEBUTTONDOWN, self.mouse_down) - self.add_callback(MOUSEMOTION, self.mouse_move) - self.is_detail = is_detail - if is_detail: - self.close_button = TextButton((0, 0), self.gd, _("Close")) - self.close_button.do_prepare() - # TODO: Don't muck around with close_button's rect - self.close_button.rect.midbottom = self.rect.midbottom - self.close_button.add_callback('clicked', self.close) - self.add(self.close_button) - - def draw(self, surface): - self.scene.draw(surface.subsurface(self.rect)) - if self.is_detail: - border = self.rect.inflate(self.DETAIL_BORDER, self.DETAIL_BORDER) - pygame.draw.rect( - surface, self.DETAIL_BORDER_COLOR, border, self.DETAIL_BORDER) - if self.parent.is_top(self): - self.scene.draw_description(surface) - super(SceneWidget, self).draw(surface) - - @property - def highlight_cursor(self): - return (self.scene.current_thing - and self.scene.current_thing.is_interactive()) - - def mouse_down(self, event, widget): - self.mouse_move(event, widget) - if event.button != 1: # We have a right/middle click - if self.game.tool: - self.game.set_tool(None) - elif self.is_detail: - self.close(event, widget) - else: - pos = self.global_to_local(event.pos) - result = self.scene.interact(self.game.tool, pos) - self.screen.handle_result(result) - - def animate(self): - self.scene.animate() - - def mouse_move(self, event, widget): - pos = self.global_to_local(event.pos) - self.scene.mouse_move(pos) - self.game.old_pos = event.pos - - def close(self, event, widget): - self.screen.close_detail(self) - - -class ToolBar(Container): - def __init__(self, pos, gd, size, screen): - self.screen = screen - - super(ToolBar, self).__init__(pos, gd, size) - - self.bg_color = (31, 31, 31) - self.left = self.rect.left - - self.menu_button = self.add_tool( - self.rect.height, TextButton, _("Menu"), - fontname=gd.constants.bold_font, - color="red", padding=1, border=0, bg_color="black") - self.menu_button.add_callback(MOUSEBUTTONDOWN, self.menu_callback) - - hand_image = gd.resource.get_image('items', 'hand.png') - self.hand_button = self.add_tool( - None, ImageButtonWidget, hand_image) - self.hand_button.add_callback(MOUSEBUTTONDOWN, self.hand_callback) - - self.inventory = self.add_tool( - self.rect.width - self.left, InventoryView, screen=screen) - - def add_tool(self, width, cls, *args, **kw): - pos = (self.left, self.rect.top) - if width is not None: - kw['size'] = (width, self.rect.height) - tool = cls(pos, self.gd, *args, **kw) - self.add(tool) - tool.do_prepare() - self.left += tool.rect.width - return tool - - def draw(self, surface): - bg = Surface(self.rect.size) - bg.fill(self.bg_color) - surface.blit(bg, self.rect) - super(ToolBar, self).draw(surface) - - def hand_callback(self, event, widget): - self.inventory.select(None) - - def menu_callback(self, event, widget): - self.screen.change_screen('menu') - - -class GameScreen(CursorScreen): - - def setup(self): - super(GameScreen, self).setup() - self.gd.running = False - self.create_initial_state = self.gd.initial_state - self.container.add_callback(KEYDOWN, self.key_pressed) - - def _clear_all(self): - self._message_queue = [] - for widget in self.container.children[:]: - self.container.remove(widget) - - def process_event(self, event_name, data): - getattr(self, 'game_event_%s' % event_name, lambda d: None)(data) - - def game_event_restart(self, data): - self.reset_game() - - def get_save_dir(self): - return self.gd.get_default_save_location() - - def game_event_load(self, data): - state = self.gd.game_state_class().load_game( - self.get_save_dir(), 'savegame') - # TODO: Handle this better. - if state is not None: - self.reset_game(state) - - def game_event_save(self, data): - self.game.data.save_game(self.get_save_dir(), 'savegame') - - def reset_game(self, game_state=None): - self._clear_all() - self.game = self.create_initial_state(game_state) - - self.screen_modal = self.container.add( - ModalStackContainer(self.container.pos, self.gd, - self.container.size)) - self.inner_container = self.screen_modal.add( - Container(self.container.pos, self.gd, self.container.size)) - - toolbar_height = self.gd.constants.button_size - - self.scene_modal = self.inner_container.add( - ModalStackContainer((0, 0), self.gd, - (self.surface_size[0], self.surface_size[1] - toolbar_height))) - self.toolbar = self.inner_container.add( - ToolBar((0, self.surface_size[1] - toolbar_height), self.gd, - (self.surface_size[0], toolbar_height), self)) - self.inventory = self.toolbar.inventory - - self.gd.running = True - - def game_event_inventory(self, data): - self.inventory.update_slots() - - def game_event_change_scene(self, data): - scene_name = data['name'] - if data['detail']: - self.show_detail(scene_name) - else: - self.change_scene(scene_name) - - def change_scene(self, scene_name): - for scene_widget in reversed(self.scene_modal.children[:]): - self.scene_modal.remove(scene_widget) - scene_widget.scene.leave() - self.game.data.set_current_scene(scene_name) - self._add_scene(self.game.scenes[scene_name]) - - def show_detail(self, detail_name): - detail_scene = self.game.detail_views[detail_name] - if detail_scene.name == self.scene_modal.top.name: - # Don't show the scene if we're already showing it - return - self._add_scene(detail_scene, True) - - def _add_scene(self, scene, detail=False): - pos = self.scene_modal.rect.topleft - size = self.scene_modal.rect.size - if detail: - size = scene.get_detail_size() - pos = ((self.scene_modal.rect.width - size[0]) / 2, - (self.scene_modal.rect.height - size[1]) / 2) - - self.scene_modal.add(SceneWidget(pos, self.gd, size, scene, self, - detail)) - self.handle_result(scene.enter()) - - def close_detail(self, detail=None): - if detail is None: - detail = self.scene_modal.top - self.scene_modal.remove(detail) - self.handle_result(detail.scene.leave()) - - def animate(self): - """Animate the scene widgets""" - for scene_widget in self.scene_modal.children: - scene_widget.animate() - - def key_pressed(self, event, widget): - if event.key == K_ESCAPE: - self.change_screen('menu') - - def end_game(self): - self.change_screen('end') - - def show_queued_widget(self): - if self._message_queue: - # Only add a message if there isn't already one up - if self.screen_modal.is_top(self.inner_container): - widget = self._message_queue.pop(0) - self.screen_modal.add( - ModalWrapper(widget, self.show_queued_widget)) - - def queue_widget(self, widget): - self._message_queue.append(widget) - self.show_queued_widget() - - def show_message(self, message): - max_width = self.gd.constants.screen[0] - 100 - widget = WrappedTextLabel((0, 0), self.gd, message, - max_width=max_width) - widget.do_prepare() - # TODO: Use the centering API when it exists - widget.rect.center = self.container.rect.center - self.queue_widget(widget) - - def handle_result(self, resultset): - """Handle dealing with result or result sequences""" - if resultset: - if hasattr(resultset, 'process'): - resultset = [resultset] - for result in resultset: - if result: - result.process(self) - - -class DefEndScreen(Screen): - """A placeholder 'Game Over' screen so people can get started easily""" - - def setup(self): - self.background = self.resource.get_image('pyntnclick/end.png') - - def draw(self, surface): - surface.blit(self.background, (0, 0)) - - -class DefMenuScreen(Screen): - """A placeholder Start screen so people can get started easily""" - - def setup(self): - self.background = self.resource.get_image('pyntnclick/start.png') - - def draw(self, surface): - surface.blit(self.background, (0, 0)) diff -r f95830b58336 -r 79b5c1be9a5e pyntnclick/i18n.py --- a/pyntnclick/i18n.py Sat Jun 21 22:04:35 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,13 +0,0 @@ -# internationalization - -from gettext import gettext -from pkg_resources import resource_filename - - -def _(s): - return unicode(gettext(s), "utf-8") - - -def get_module_i18n_path(module, path='locale'): - """Get the locale data from within the module.""" - return resource_filename(module, path) diff -r f95830b58336 -r 79b5c1be9a5e pyntnclick/image_transforms.py --- a/pyntnclick/image_transforms.py Sat Jun 21 22:04:35 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,63 +0,0 @@ -"""Transforms to apply to images when they're loaded.""" - -from pygame.transform import rotate -from pygame.locals import BLEND_RGBA_MULT, SRCALPHA -from pygame.surface import Surface - - -class Transform(object): - - def __init__(self, func, *args): - self._func = func - self._args = args - - def __call__(self, image): - return self._func(image, *self._args) - - def __hash__(self): - return hash((id(self._func), self._args)) - - def __eq__(self, other): - return (self._func is other._func) and self._args == other._args - - def __repr__(self): - return "<%s args=%r>" % (self.__class__.__name__, self._args) - - -# transform that does nothing -NULL = Transform(lambda x: x) - -# base rotation transforms -R90 = Transform(rotate, 90) -R180 = Transform(rotate, 180) -R270 = Transform(rotate, -90) - - -# overlays -class Overlay(Transform): - """Overlay another image on top of the given one.""" - - def __init__(self, resources, image_name_fragments, blend=0): - super(Overlay, self).__init__( - self.overlay, resources, image_name_fragments, blend) - - def overlay(self, image, resources, image_name_fragments, blend): - image = image.copy() - overlay = resources.load_image(image_name_fragments) - image.blit(overlay, (0, 0), None, blend) - return image - - -# colour overlays -class Colour(Transform): - """Overlay an image with a colour.""" - - def __init__(self, colour, blend=BLEND_RGBA_MULT): - super(Colour, self).__init__(self.colour, colour, blend) - - def colour(self, image, colour, blend): - image = image.copy() - overlay = Surface(image.get_size(), SRCALPHA, image) - overlay.fill(colour) - image.blit(overlay, (0, 0), None, blend) - return image diff -r f95830b58336 -r 79b5c1be9a5e pyntnclick/main.py --- a/pyntnclick/main.py Sat Jun 21 22:04:35 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,214 +0,0 @@ -'''Game main module. - -Contains the entry point used by the run_game.py script. - -''' -import sys -import gettext -import locale -import os -from optparse import OptionParser - -import pygame -from pygame.locals import SWSURFACE - -from pyntnclick.i18n import _, get_module_i18n_path -from pyntnclick.engine import Engine -from pyntnclick.gamescreen import DefMenuScreen, DefEndScreen, GameScreen -from pyntnclick.constants import GameConstants, DEBUG_ENVVAR -from pyntnclick.resources import Resources -from pyntnclick.sound import Sound -from pyntnclick import state - -from pyntnclick.tools.rect_drawer import (RectEngine, RectDrawerError, - make_rect_display) -from pyntnclick.utils import list_scenes - - -class GameDescriptionError(Exception): - """Raised when an GameDescription is invalid.""" - - -class GameDescription(object): - - # initial scene for start of game (unless overridden by debug) - INITIAL_SCENE = None - - # list of game scenes - SCENE_LIST = None - - # starting menu - SCREENS = { - 'menu': DefMenuScreen, - 'end': DefEndScreen, - } - - START_SCREEN = 'menu' - - # Modules - RESOURCE_MODULE = 'data' - SCENE_MODULE = 'gamelib.scenes' - - def __init__(self): - if self.INITIAL_SCENE is None: - raise GameDescriptionError("A game must have an initial scene.") - if not self.SCENE_LIST: - raise GameDescriptionError("A game must have a non-empty list" - " of scenes.") - if 'game' in self.SCREENS: - raise GameDescriptionError("The 'game' screen is reserved for the" - " game itself.") - self._initial_scene = self.INITIAL_SCENE - self._scene_list = self.SCENE_LIST - self._resource_module = self.RESOURCE_MODULE - self._debug_rects = False - self._screens = self.SCREENS.copy() - self._screens['game'] = GameScreen - self.constants = self.game_constants() - - locale.setlocale(locale.LC_ALL, "") - lang = locale.getdefaultlocale(['LANGUAGE', 'LC_ALL', 'LC_CTYPE', - 'LANG'])[0] - self.resource = Resources(self._resource_module, lang) - locale_path = self.resource.get_resource_path('locale') - gettext.bindtextdomain(self.constants.short_name, locale_path) - gettext.textdomain(self.constants.short_name) - - popath = self.resource.get_resource_path('po') - self._check_translations(popath, locale_path) - - self.sound = Sound(self.resource) - self.debug_options = [] - self.running = False - - def _check_translations(self, popath, locale_path): - """Check for outdated mo files""" - name = gettext.textdomain() # only check the current app - for candidate in os.listdir(popath): - if candidate.endswith('.po'): - polang = candidate.split('.', 1)[0] - pofile = os.path.join(popath, candidate) - mofile = gettext.find(name, locale_path, (polang,)) - if mofile is None: - print 'Missing mo file for %s' % pofile - continue - if os.stat(pofile).st_mtime > os.stat(mofile).st_mtime: - print 'po file %s is newer than mo file %s' % (pofile, - mofile) - - def initial_state(self, game_state=None): - """Create a copy of the initial game state.""" - initial_state = state.Game(self, self.game_state_class()(game_state)) - initial_state.set_debug_rects(self._debug_rects) - for scene in self._scene_list: - initial_state.load_scenes(scene) - if initial_state.data['current_scene'] is None: - initial_state.data.set_current_scene(self._initial_scene) - initial_state.change_scene(initial_state.data['current_scene']) - return initial_state - - def game_state_class(self): - return state.GameState - - def game_constants(self): - return GameConstants() - - def option_parser(self): - parser = OptionParser() - parser.add_option("--no-sound", action="store_false", default=True, - dest="sound", help="disable sound") - # We flag these, so we can warn the user that these require debug mode - self.debug_options = ['--scene', '--no-rects', '--rect-drawer', - '--list-scenes', '--details'] - if self.constants.debug: - parser.add_option("--scene", type="str", default=None, - dest="scene", help="initial scene") - parser.add_option("--no-rects", action="store_false", default=True, - dest="rects", help="disable debugging rects") - parser.add_option("--rect-drawer", action="store_true", - default=False, dest="rect_drawer", - help="Launch the rect drawing helper tool. Specify the" - " scene with --scene") - parser.add_option("--list-scenes", action="store_true", - default=False, dest='list_scenes', help="List all scenes" - " that can be used with --scene and exit.") - parser.add_option("--detail", type="str", default=None, - dest="detail", help="Detailed view for rect_drawer") - return parser - - def warn_debug(self, option): - """Warn the user that he needs debug mode""" - print '%s is only valid in debug mode' % option - print 'set %s to enable debug mode' % DEBUG_ENVVAR - print - - def main(self): - parser = self.option_parser() - # This is a bit hack'ish, but works - if not self.constants.debug: - for option in self.debug_options: - if option in sys.argv: - self.warn_debug(option) - opts, args = parser.parse_args(sys.argv) - pygame.display.init() - pygame.font.init() - if opts.sound: - self.sound.enable_sound(self.constants) - else: - self.sound.disable_sound() - if self.constants.debug: - if opts.scene is not None: - # debug the specified scene - self._initial_scene = opts.scene - self._debug_rects = opts.rects - if self.constants.debug and opts.list_scenes: - list_scenes(self.SCENE_MODULE, self._scene_list) - sys.exit(0) - if self.constants.debug and opts.rect_drawer: - if opts.scene is None: - print 'Need to supply a scene to use the rect drawer' - sys.exit(1) - locale_path = get_module_i18n_path( - self.resource.DEFAULT_RESOURCE_MODULE) - gettext.bindtextdomain('pyntnclick-tools', locale_path) - gettext.textdomain('pyntnclick-tools') - popath = get_module_i18n_path( - self.resource.DEFAULT_RESOURCE_MODULE, 'po') - self._check_translations(popath, locale_path) - make_rect_display() - try: - self.engine = RectEngine(self, opts.detail) - except RectDrawerError, e: - print 'RectDrawer failed with: %s' % e - sys.exit(1) - else: - pygame.display.set_mode(self.constants.screen, SWSURFACE) - if self.constants.icon: - pygame.display.set_icon(self.resource.get_image( - self.constants.icon, basedir='icons')) - if self.constants.title: - title = _(self.constants.title).encode('utf-8') - pygame.display.set_caption(title) - - self.engine = Engine(self) - # Initialize the special screens in the engine - for name, cls in self._screens.iteritems(): - screen = cls(self) - self.engine.add_screen(name, screen) - # Should we allow the menu not to be the opening screen? - self.engine.set_screen(self.START_SCREEN) - try: - self.engine.run() - except KeyboardInterrupt: - pass - - def get_default_save_location(self): - """Return a default save game location.""" - app = self.constants.short_name - if sys.platform.startswith("win"): - if "APPDATA" in os.environ: - return os.path.join(os.environ["APPDATA"], app) - return os.path.join(os.path.expanduser("~"), "." + app) - elif 'XDG_DATA_HOME' in os.environ: - return os.path.join(os.environ["XDG_DATA_HOME"], app) - return os.path.join(os.path.expanduser("~"), ".local", "share", app) diff -r f95830b58336 -r 79b5c1be9a5e pyntnclick/menuscreen.py --- a/pyntnclick/menuscreen.py Sat Jun 21 22:04:35 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,119 +0,0 @@ -# menu.py -# Copyright Boomslang team, 2010 (see COPYING File) -# Main menu for the game - -import pygame.event -from pygame.locals import QUIT -from pyntnclick.engine import Screen -from pyntnclick.widgets.imagebutton import ImageButtonWidget -from pyntnclick.widgets.text import TextButton - - -class MenuScreen(Screen): - BACKGROUND_IMAGE = None - - def setup(self): - self._background = None - if self.BACKGROUND_IMAGE is not None: - self._background = self.resource.get_image(self.BACKGROUND_IMAGE) - - self._add_new_game_button() - self._add_load_game_button() - self._add_save_game_button() - self._add_resume_game_button() - self._add_quit_button() - - def on_enter(self): - super(MenuScreen, self).on_enter() - running = self.check_running() - self.set_button_state(self._resume_game_button, running) - self.set_button_state(self._load_game_button, self.check_has_saves()) - self.set_button_state(self._save_game_button, running) - - def set_button_state(self, button, enabled): - button.set_visible(enabled) - if enabled: - button.enable() - else: - button.disable() - - def make_new_game_button(self): - "Override this to customise the new game button." - return self.make_text_button((200, 100), 'New game') - - def make_load_game_button(self): - "Override this to customise the load game button." - return self.make_text_button((200, 200), 'Load game') - - def make_save_game_button(self): - "Override this to customise the save game button." - return self.make_text_button((200, 300), 'Save game') - - def make_resume_button(self): - "Override this to customise the resume game button." - return self.make_text_button((200, 400), 'Resume') - - def make_quit_button(self): - "Override this to customise the quit button." - return self.make_text_button((200, 500), 'Quit') - - def _add_new_game_button(self): - self._new_game_button = self.make_new_game_button() - self._new_game_button.add_callback('clicked', self.new_game) - - def _add_load_game_button(self): - self._load_game_button = self.make_load_game_button() - self._load_game_button.add_callback('clicked', self.load_game) - - def _add_save_game_button(self): - self._save_game_button = self.make_save_game_button() - self._save_game_button.add_callback('clicked', self.save_game) - - def _add_resume_game_button(self): - self._resume_game_button = self.make_resume_game_button() - self._resume_game_button.add_callback('clicked', self.resume_game) - - def _add_quit_button(self): - self._quit_button = self.make_quit_button() - self._quit_button.add_callback('clicked', self.quit) - - def make_text_button(self, pos, text): - widget = TextButton(pos, self.gd, text) - self.container.add(widget) - return widget - - def make_image_button(self, pos, image_name): - image = self.resource.get_image(image_name) - widget = ImageButtonWidget(pos, self.gd, image) - self.container.add(widget) - return widget - - def draw_background(self): - if self._background is not None: - self.surface.blit(self._background, self.surface.get_rect()) - - def new_game(self, ev, widget): - self.screen_event('game', 'restart') - self.change_screen('game') - - def load_game(self, ev, widget): - self.screen_event('game', 'load') - self.change_screen('game') - - def save_game(self, ev, widget): - self.screen_event('game', 'save') - - def check_running(self): - return self.gd.running - - def check_has_saves(self): - import os.path - save_dir = self.gd.get_default_save_location() - return os.path.exists( - self.gd.game_state_class().get_save_fn(save_dir, 'savegame')) - - def resume_game(self, ev, widget): - self.change_screen('game') - - def quit(self, ev, widget): - pygame.event.post(pygame.event.Event(QUIT)) diff -r f95830b58336 -r 79b5c1be9a5e pyntnclick/resources.py --- a/pyntnclick/resources.py Sat Jun 21 22:04:35 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,117 +0,0 @@ -# -*- test-case-name: pyntnclick.tests.test_resources -*- - -import os -from pkg_resources import resource_filename - -import pygame - - -class ResourceNotFound(Exception): - pass - - -class Resources(object): - """Resource loader and manager. - - The `CONVERT_ALPHA` flag allows alpha conversions to be disabled so that - images may be loaded without having a display initialised. This is useful - in unit tests, for example. - """ - - DEFAULT_RESOURCE_MODULE = "pyntnclick.data" - CONVERT_ALPHA = True - - def __init__(self, resource_module, language=None): - self.resource_module = resource_module - self.lang_dialect = language - self.language = language - if language: - self.language = language.split('_', 1)[0] - self._image_cache = {} - self._font_cache = {} - self._transformed_image_cache = {} - - def get_resource_path(self, *resource_path_fragments): - """Find the resource in one of a number of different places. - - The following directories are searched, in order: - - * /_// - * /// - * / - * /_// - * /// - * / - - If the `language` attribute is `None`, the paths with in them - are skipped. - """ - resource_name = '/'.join(resource_path_fragments) - resource_name = os.path.join(*resource_name.split('/')) - for path in self.get_paths(resource_name): - if os.path.exists(path): - return path - raise ResourceNotFound(resource_name) - - def get_paths(self, resource_path): - """Get list of resource paths to search. - """ - paths = [] - for module in [self.resource_module, self.DEFAULT_RESOURCE_MODULE]: - if self.lang_dialect: - fn = os.path.join(self.lang_dialect, resource_path) - paths.append(resource_filename(module, fn)) - if self.language != self.lang_dialect: - fn = os.path.join(self.language, resource_path) - paths.append(resource_filename(module, fn)) - paths.append(resource_filename(module, resource_path)) - return paths - - def get_image(self, *image_name_fragments, **kw): - """Load an image and optionally apply mutators. - - All positional params end up in `image_name_fragments` and are joined - with the path separator. - - Two keyword parameters are also accepted: - - * `transforms` may contain transforms, which modify an image in-place - to apply various effects. - - * `basedir` defaults to 'images', but may be overridden to load images - from other places. ('icons', for example.) - """ - - transforms = kw.get('transforms', ()) - basedir = kw.get('basedir', 'images') - - image_path = self.get_resource_path(basedir, *image_name_fragments) - - key = (image_path, transforms) - if key in self._transformed_image_cache: - # We already have this cached, so shortcut the whole process. - return self._transformed_image_cache[key] - - if image_path not in self._image_cache: - image = pygame.image.load(image_path) - if self.CONVERT_ALPHA: - image = image.convert_alpha(pygame.display.get_surface()) - self._image_cache[image_path] = image - image = self._image_cache[image_path] - - # Apply any transforms we're given. - for transform in transforms: - image = transform(image) - self._transformed_image_cache[key] = image - - return image - - def get_font(self, file_name, font_size, basedir=None): - """Load a a font, cached if possible.""" - if basedir is None: - basedir = 'fonts' - key = (basedir, file_name, font_size) - if key not in self._font_cache: - fontfn = self.get_resource_path(basedir, file_name) - self._font_cache[key] = pygame.font.Font(fontfn, font_size) - return self._font_cache[key] diff -r f95830b58336 -r 79b5c1be9a5e pyntnclick/scenewidgets.py --- a/pyntnclick/scenewidgets.py Sat Jun 21 22:04:35 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,223 +0,0 @@ -"""Interactive elements within a Scene.""" - - -from pygame import Rect -from pygame.color import Color -from pygame.colordict import THECOLORS -from pygame.surface import Surface - -from pyntnclick.state import Thing -from pyntnclick.utils import convert_color, render_text -from pyntnclick.widgets.text import LabelWidget - - -class Interact(object): - - def __init__(self, image, rect, interact_rect): - self.image = image - self.rect = rect - self.interact_rect = interact_rect - - def set_thing(self, thing): - pass - - def draw(self, surface): - if self.image is not None: - surface.blit(self.image, self.rect, None) - - def animate(self): - return False - - -class InteractNoImage(Interact): - - def __init__(self, x, y, w, h): - super(InteractNoImage, self).__init__(None, None, Rect(x, y, w, h)) - - -class InteractDebugText(Interact): - """Display box with text to interact with -- mostly for debugging.""" - - def __init__(self, x, y, text, bg_color=None): - if bg_color is None: - bg_color = (127, 127, 127) - label = LabelWidget((0, 0), text) - # label.set_margin(5) - # label.border_width = 1 - # label.border_color = (0, 0, 0) - # label.bg_color = bg_color - # label.fg_color = (0, 0, 0) - image = Surface(label.size) - rect = Rect((x, y), label.size) - label.draw_all(image) - super(InteractDebugText, self).__init__(image, rect, rect) - - -class InteractText(Interact): - """Display a text string on a transparent background. - - Used so we can easily include translatable strings in the scenes""" - - def __init__(self, x, y, w, h, text, color, max_font_size, font=None, - centre=True): - self._text = text - self._color = convert_color(color) - self._max_font_size = max_font_size - self._font = font - self._centre = centre - rect = Rect((x, y), (w, h)) - super(InteractText, self).__init__(None, rect, rect) - - def set_thing(self, thing): - font_size = self._max_font_size - if not self._font: - # Pull the default font out of constants - self._font = thing.gd.constants.font - bg_color = Color(0, 0, 0, 0) # transparent background - self.image = render_text(self._text, self._font, font_size, - self._color, bg_color, thing.resource, self.rect.size, - self._centre) - - -class InteractRectUnion(Interact): - - def __init__(self, rect_list): - super(InteractRectUnion, self).__init__(None, None, None) - rect_list = [Rect(x) for x in rect_list] - self.interact_rect = rect_list - - -class InteractUnion(Interact): - """An interact made out of other interacts""" - - def __init__(self, interact_list): - super(InteractUnion, self).__init__(None, None, None) - self._interact_list = interact_list - - def set_thing(self, thing): - interact_list = [] - for sub_interact in self._interact_list: - sub_interact.set_thing(thing) - sub_rect = sub_interact.interact_rect - if hasattr(sub_rect, 'collidepoint'): - interact_list.append(sub_interact.interact_rect) - else: - interact_list.extend(sub_interact.interact_rect) - self.interact_rect = interact_list - - def draw(self, surface): - for sub_interact in self._interact_list: - sub_interact.draw(surface) - - def animate(self): - for sub_interact in self._interact_list: - sub_interact.animate() - - -class InteractImage(Interact): - - def __init__(self, x, y, image_name): - super(InteractImage, self).__init__(None, None, None) - self._pos = (x, y) - self._image_name = image_name - - def set_thing(self, thing): - self.image = thing.resource.get_image(thing.folder, self._image_name) - self.rect = Rect(self._pos, self.image.get_size()) - self.interact_rect = self.rect - - def __repr__(self): - return '' % self._image_name - - -class InteractImageRect(InteractImage): - def __init__(self, x, y, image_name, r_x, r_y, r_w, r_h): - super(InteractImageRect, self).__init__(x, y, image_name) - self._r_pos = (r_x, r_y) - self._r_size = (r_w, r_h) - - def set_thing(self, thing): - super(InteractImageRect, self).set_thing(thing) - self.interact_rect = Rect(self._r_pos, self._r_size) - - -class InteractAnimated(Interact): - """Interactive with an animation rather than an image""" - - # anim_seq - sequence of image names - # delay - number of frames to wait between changing images - - def __init__(self, x, y, anim_seq, delay): - self._pos = (x, y) - self._anim_pos = 0 - self._names = anim_seq - self._frame_count = 0 - self._anim_seq = None - self._delay = delay - - def set_thing(self, thing): - self._anim_seq = [thing.resource.get_image(thing.folder, x) - for x in self._names] - self.image = self._anim_seq[0] - self.rect = Rect(self._pos, self.image.get_size()) - for image in self._anim_seq: - assert image.get_size() == self.rect.size - self.interact_rect = self.rect - - def animate(self): - if self._anim_seq: - self._frame_count += 1 - if self._frame_count > self._delay: - self._frame_count = 0 - self._anim_pos += 1 - if self._anim_pos >= len(self._anim_seq): - self._anim_pos = 0 - self.image = self._anim_seq[self._anim_pos] - # queue redraw - return True - return False - - -class TakeableThing(Thing): - "Thing that can be taken." - - INITIAL_DATA = { - 'taken': False, - } - - ITEM = None - - def __init__(self): - # In case a subclass replaces INITIAL_DATA and breaks 'taken'. - assert self.INITIAL_DATA['taken'] in (True, False) - super(TakeableThing, self).__init__() - - def should_add(self): - return not self.get_data('taken') - - def take(self): - self.set_data('taken', True) - self.game.add_inventory_item(self.ITEM) - self.scene.remove_thing(self) - - -class GenericDescThing(Thing): - "Thing with an InteractiveUnionRect and a description" - - INITIAL = "description" - - def __init__(self, prefix, number, description, areas): - super(GenericDescThing, self).__init__() - self.description = description - self.name = '%s.%s' % (prefix, number) - self.interacts = { - 'description': InteractRectUnion(areas) - } - # Individual colors to make debugging easier - self._interact_hilight_color = Color(THECOLORS.keys()[number]) - - def get_description(self): - return self.description - - def is_interactive(self, tool=None): - return False diff -r f95830b58336 -r 79b5c1be9a5e pyntnclick/sound.py --- a/pyntnclick/sound.py Sat Jun 21 22:04:35 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,159 +0,0 @@ -# sound management for pyntnclick - -from random import randrange - - -import pygame - -try: - from pygame.mixer import Sound as pygame_Sound - from pygame.mixer import music - pygame_import_error = None -except ImportError, e: - # Save error, so we don't crash and can do the right thing later - pygame_import_error = e - pygame_Sound = None - music = None - -from pyntnclick.resources import ResourceNotFound -from pyntnclick.engine import MUSIC_ENDED - - -class PlayList(object): - """Hold a playlist of music filenames""" - - def __init__(self, pieces, random, repeat): - self._pieces = pieces - self._random = random - self._repeat = repeat - - def get_next(self): - # Get the next piece - if self._pieces: - if self._random: - if not self._repeat or len(self._pieces) < 3: - i = randrange(0, len(self._pieces)) - else: - # Ignore the last entry, since we possibly just played it - i = randrange(0, len(self._pieces) - 1) - else: - i = 0 - result = self._pieces.pop(i) - if self._repeat: - self._pieces.append(result) - return result - return None - - -class DummySound(object): - """A dummy sound object. - - This is a placeholder object with the same API as - pygame.mixer.Sound which does nothing. Used when - sounds are disabled so scense don't need to worry - about the details. - - Inpsired by the same idea in Albow (by Greg Ewing)""" - - def play(self, *args): - pass - - def stop(self): - pass - - def get_length(self): - return 0.0 - - def get_num_channel(self): - return 0 - - def get_volume(self): - return 0.0 - - def fadeout(self, *args): - pass - - -class Sound(object): - """Global sound management and similiar useful things""" - - def __init__(self, resource_finder): - self.sound_enabled = False - self.sound_cache = {} - self._resource_finder = resource_finder - self._current_playlist = None - - def enable_sound(self, constants): - """Attempt to initialise the sound system""" - if pygame_Sound is None: - self.disable_sound(pygame_import_error) - return - try: - pygame.mixer.init(constants.snd_freq, - constants.snd_bitsize, - constants.snd_channels, - constants.snd_buffer) - self.sound_enabled = True - music.set_endevent(MUSIC_ENDED) - except pygame.error, exc: - self.disable_sound(exc) - - def disable_sound(self, exc=None): - """Disable the sound system""" - self.sound_enabled = False - if exc is not None: - print 'Failed to initialise sound system' - print 'Error: %s' % exc - print 'Sound disabled' - - def get_sound(self, *names): - if not self.sound_enabled: - return DummySound() - sound = None - try: - path = self._resource_finder.get_resource_path("sounds", *names) - sound = self.sound_cache.get(path, None) - except ResourceNotFound: - print "Sound file not found: %s" % names - # Cache failed lookup - sound = DummySound() - self.sound_cache[path] = sound - if sound is None: - try: - sound = pygame_Sound(path) - except pygame.error: - print "Sound file not found: %s" % names - sound = DummySound() - self.sound_cache[path] = sound - return sound - - def get_playlist(self, pieces, random=False, repeat=False): - return PlayList(pieces, random, repeat) - - def get_music(self, name): - if self.sound_enabled: - music_file = self._resource_finder.get_resource_path("sounds", - name) - return music_file - return None - - def music_ended(self): - if self._current_playlist: - # Try start the next tune - self.start_next_music() - - def change_playlist(self, new_playlist): - if self.sound_enabled: - music.stop() - self._current_playlist = new_playlist - self.start_next_music() - - def start_next_music(self): - if self._current_playlist: - tune = self._current_playlist.get_next() - if tune: - music.load(tune) - music.play() - - def get_current_playlist(self): - return self._current_playlist diff -r f95830b58336 -r 79b5c1be9a5e pyntnclick/speech.py --- a/pyntnclick/speech.py Sat Jun 21 22:04:35 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,44 +0,0 @@ -# speech.py -# Copyright Boomslang team, 2010 (see COPYING File) -# Speech playing and cache - -import re - -from pyntnclick.sound import get_sound - - -# cache of string -> sound object mappings -_SPEECH_CACHE = {} - -# characters not to allow in filenames -_REPLACE_RE = re.compile(r"[^a-z0-9-]+") - - -class SpeechError(RuntimeError): - pass - - -def get_filename(key, text): - """Simplify text to filename.""" - filename = "%s-%s" % (key, text) - filename = filename.lower() - filename = _REPLACE_RE.sub("_", filename) - filename = filename[:30] - filename = "%s.ogg" % filename - return filename - - -def get_speech(thing_name, text): - """Load a sound object from the cache.""" - key = (thing_name, text) - if key in _SPEECH_CACHE: - return _SPEECH_CACHE[key] - filename = get_filename(thing_name, text) - _SPEECH_CACHE[key] = sound = get_sound("speech", filename) - return sound - - -def say(thing_name, text): - """Play text as speech.""" - sound = get_speech(thing_name, text) - sound.play() diff -r f95830b58336 -r 79b5c1be9a5e pyntnclick/state.py --- a/pyntnclick/state.py Sat Jun 21 22:04:35 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,660 +0,0 @@ -"""Utilities and base classes for dealing with scenes.""" - -import os -import json -import copy - -from widgets.text import LabelWidget -from pygame.color import Color - -from pyntnclick.engine import ScreenEvent -from pyntnclick.utils import draw_rect_image - - -class Result(object): - """Result of interacting with a thing""" - - def __init__(self, message=None, soundfile=None, detail_view=None, - widget=None, end_game=False): - self.message = message - self.soundfile = soundfile - self.detail_view = detail_view - self.widget = widget - self.end_game = end_game - - def play_sound(self, screen): - if self.soundfile: - sound = screen.gd.sound.get_sound(self.soundfile) - sound.play() - - def process(self, screen): - """Helper function to do the right thing with a result object""" - self.play_sound(screen) - if self.widget: - screen.queue_widget(self.widget) - if self.message: - screen.show_message(self.message) - if self.detail_view: - screen.game.show_detail(self.detail_view) - if self.end_game: - screen.end_game() - - -class GameState(object): - """This holds the serializable game state. - - Games wanting to do fancier stuff with the state should - sub-class this and feed the subclass into - GameDescription via the custom_data parameter.""" - - def __init__(self, state_dict=None): - if state_dict is None: - state_dict = { - 'inventories': {'main': []}, - 'item_factories': {}, - 'current_scene': None, - } - self._game_state = copy.deepcopy(state_dict) - - def __getitem__(self, key): - return self._game_state[key] - - def __contains__(self, key): - return key in self._game_state - - def export_data(self): - return copy.deepcopy(self._game_state) - - def get_data(self, state_key, data_key): - """Get a single entry""" - return self[state_key].get(data_key, None) - - def set_data(self, state_key, data_key, value): - """Set a single value""" - self[state_key][data_key] = value - - def _initialize_state(self, state_dict, state_key, initial_data): - if state_key not in self._game_state: - state_dict[state_key] = copy.deepcopy(initial_data) - - def initialize_state(self, state_key, initial_data): - """Initialize a gizmo entry""" - self._initialize_state(self._game_state, state_key, initial_data) - - def initialize_item_factory_state(self, state_key, initial_data): - """Initialize an item factory entry""" - self._initialize_state( - self._game_state['item_factories'], state_key, initial_data) - - def inventory(self, name='main'): - return self['inventories'][name] - - def set_current_scene(self, scene_name): - self._game_state['current_scene'] = scene_name - - @classmethod - def get_save_fn(cls, save_dir, save_name): - return os.path.join(save_dir, '%s.json' % (save_name,)) - - @classmethod - def load_game(cls, save_dir, save_name): - fn = cls.get_save_fn(save_dir, save_name) - if os.access(fn, os.R_OK): - f = open(fn, 'r') - state = json.load(f) - f.close() - return state - - def save_game(self, save_dir, save_name): - fn = self.get_save_fn(save_dir, save_name) - if not os.path.isdir(save_dir): - os.makedirs(save_dir) - f = open(fn, 'w') - json.dump(self.export_data(), f) - f.close() - - -class Game(object): - """Complete game state. - - Game state consists of: - - * items - * scenes - """ - def __init__(self, gd, game_state): - # game description - self.gd = gd - # map of scene name -> Scene object - self.scenes = {} - # map of detail view name -> DetailView object - self.detail_views = {} - # map of item prefix -> ItemFactory object - self.item_factories = {} - # list of item objects in inventory - self.current_inventory = 'main' - # currently selected tool (item) - self.tool = None - # Global game data - self.data = game_state - # debug rects - self.debug_rects = False - - def get_current_scene(self): - scene_name = self.data['current_scene'] - if scene_name is not None: - return self.scenes[scene_name] - return None - - def get_item(self, item_name): - base_name, _, _suffix = item_name.partition(':') - factory = self.item_factories[base_name] - return factory.get_item(item_name) - - def create_item(self, base_name): - assert ":" not in base_name - factory = self.item_factories[base_name] - return factory.create_item() - - def inventory(self, name=None): - if name is None: - name = self.current_inventory - return self.data.inventory(name) - - def set_custom_data(self, data_object): - self.data = data_object - - def set_debug_rects(self, value=True): - self.debug_rects = value - - def add_scene(self, scene): - scene.set_game(self) - self.scenes[scene.name] = scene - - def add_detail_view(self, detail_view): - detail_view.set_game(self) - self.detail_views[detail_view.name] = detail_view - - def add_item_factory(self, item_class): - name = item_class.NAME - assert name not in self.item_factories, ( - "Factory for %s already added." % (name,)) - factory = item_class.ITEM_FACTORY(item_class) - factory.set_game(self) - self.item_factories[name] = factory - - def load_scenes(self, modname): - mod = __import__('%s.%s' % (self.gd.SCENE_MODULE, modname), - fromlist=[modname]) - for scene_cls in mod.SCENES: - scene = scene_cls(self) - self.add_scene(scene) - if hasattr(mod, 'DETAIL_VIEWS'): - for scene_cls in mod.DETAIL_VIEWS: - scene = scene_cls(self) - self.add_detail_view(scene) - - def change_scene(self, name): - ScreenEvent.post('game', 'change_scene', - {'name': name, 'detail': False}) - - def show_detail(self, name): - ScreenEvent.post('game', 'change_scene', - {'name': name, 'detail': True}) - - def _update_inventory(self): - ScreenEvent.post('game', 'inventory', None) - - def add_inventory_item(self, item_name): - item = self.create_item(item_name) - self.inventory().append(item.name) - self._update_inventory() - - def is_in_inventory(self, name): - return name in self.inventory() - - def remove_inventory_item(self, name): - self.inventory().remove(name) - # Unselect tool if it's removed - if self.tool == self.get_item(name): - self.set_tool(None) - self._update_inventory() - - def replace_inventory_item(self, old_item_name, new_item_name): - """Try to replace an item in the inventory with a new one""" - try: - index = self.inventory().index(old_item_name) - new_item = self.create_item(new_item_name) - self.inventory()[index] = new_item.name - if self.tool == self.get_item(old_item_name): - self.set_tool(new_item) - except ValueError: - return False - self._update_inventory() - return True - - def set_tool(self, item): - self.tool = item - - -class GameDeveloperGizmo(object): - """Base class for objects game developers see.""" - - def __init__(self): - """Set """ - self.game = None - self.gd = None - self.resource = None - self.sound = None - - def set_game(self, game): - self.game = game - self.gd = game.gd - self.resource = self.gd.resource - self.sound = self.gd.sound - self.set_state(self.game.data) - self.setup() - - def set_state(self, state): - """Hack to allow set_state() to be called before setup().""" - pass - - def setup(self): - """Game developers should override this to do their setup. - - It will be called after all the useful state functions have been - set. - """ - pass - - -class StatefulGizmo(GameDeveloperGizmo): - - # initial data (optional, defaults to none) - INITIAL_DATA = None - STATE_KEY = None - - def __init__(self): - GameDeveloperGizmo.__init__(self) - self.state_key = self.STATE_KEY - self.state = None # set this with set_state if required - - def set_state(self, state): - """Set the state object and initialize if needed""" - self.state = state - if self.state_key is None: - assert self.INITIAL_DATA is None, ( - "Can't provide self.INITIAL_DATA without self.state_key.") - if self.INITIAL_DATA is not None: - self.state.initialize_state(self.state_key, self.INITIAL_DATA) - - def set_data(self, key, value): - if self.state: - self.state.set_data(self.state_key, key, value) - - def get_data(self, key): - if self.state: - return self.state.get_data(self.state_key, key) - - -class Scene(StatefulGizmo): - """Base class for scenes.""" - - # sub-folder to look for resources in - FOLDER = None - - # name of background image resource - BACKGROUND = None - - # name of scene (optional, defaults to folder) - NAME = None - - # Offset of the background image - OFFSET = (0, 0) - - def __init__(self, state): - StatefulGizmo.__init__(self) - # scene name - self.name = self.NAME if self.NAME is not None else self.FOLDER - self.state_key = self.name - # map of thing names -> Thing objects - self.things = {} - self.current_thing = None - self._background = None - - def add_item_factory(self, item_factory): - self.game.add_item_factory(item_factory) - - def add_thing(self, thing): - thing.set_game(self.game) - if not thing.should_add(): - return - self.things[thing.name] = thing - thing.set_scene(self) - - def remove_thing(self, thing): - del self.things[thing.name] - if thing is self.current_thing: - self.current_thing.leave() - self.current_thing = None - - def _get_description(self, dest_rect): - text = (self.current_thing and - self.current_thing.get_description()) - if text is None: - return None - label = LabelWidget((0, 10), self.gd, text) - label.do_prepare() - # TODO: Centre more cleanly - label.rect.left += (dest_rect.width - label.rect.width) / 2 - return label - - def draw_description(self, surface): - description = self._get_description(surface.get_rect()) - if description is not None: - description.draw(surface) - - def _cache_background(self): - if self.BACKGROUND and not self._background: - self._background = self.resource.get_image( - self.FOLDER, self.BACKGROUND) - - def draw_background(self, surface): - self._cache_background() - if self._background is not None: - surface.blit(self._background, self.OFFSET, None) - else: - surface.fill((200, 200, 200)) - - def draw_things(self, surface): - for thing in self.things.itervalues(): - thing.draw(surface) - - def draw(self, surface): - self.draw_background(surface) - self.draw_things(surface) - - def interact(self, item, pos): - """Interact with a particular position. - - Item may be an item in the list of items or None for the hand. - - Returns a Result object to provide feedback to the player. - """ - if self.current_thing is not None: - return self.current_thing.interact(item) - - def animate(self): - """Animate all the things in the scene. - - Return true if any of them need to queue a redraw""" - result = False - for thing in self.things.itervalues(): - if thing.animate(): - result = True - return result - - def enter(self): - return None - - def leave(self): - return None - - def update_current_thing(self, pos): - if self.current_thing is not None: - if not self.current_thing.contains(pos): - self.current_thing.leave() - self.current_thing = None - for thing in self.things.itervalues(): - if thing.contains(pos): - thing.enter(self.game.tool) - self.current_thing = thing - break - - def mouse_move(self, pos): - """Call to check whether the cursor has entered / exited a thing. - - Item may be an item in the list of items or None for the hand. - """ - self.update_current_thing(pos) - - def get_detail_size(self): - self._cache_background() - return self._background.get_size() - - def get_image(self, *image_name_fragments, **kw): - return self.resource.get_image(*image_name_fragments, **kw) - - def set_state(self, state): - return super(Scene, self).set_state(state) - - -class InteractiveMixin(object): - def is_interactive(self, tool=None): - return True - - def interact(self, tool): - if not self.is_interactive(tool): - return None - if tool is None: - return self.interact_without() - handler = getattr(self, 'interact_with_' + tool.tool_name, None) - inverse_handler = self.get_inverse_interact(tool) - if handler is not None: - return handler(tool) - elif inverse_handler is not None: - return inverse_handler(self) - else: - return self.interact_default(tool) - - def get_inverse_interact(self, tool): - return None - - def interact_without(self): - return self.interact_default(None) - - def interact_default(self, item=None): - return None - - -class Thing(StatefulGizmo, InteractiveMixin): - """Base class for things in a scene that you can interact with.""" - - # name of thing - NAME = None - - # sub-folder to look for resources in (defaults to scenes folder) - FOLDER = None - - # list of Interact objects - INTERACTS = {} - - # name first interact - INITIAL = None - - # Interact rectangle hi-light color (for debugging) - # (set to None to turn off) - _interact_hilight_color = Color('red') - - def __init__(self): - StatefulGizmo.__init__(self) - # name of the thing - self.name = self.NAME - # folder for resource (None is overridden by scene folder) - self.folder = self.FOLDER - self.state_key = self.NAME - # interacts - self.interacts = self.INTERACTS - # these are set by set_scene - self.scene = None - self.current_interact = None - self.rect = None - self.orig_rect = None - - def _fix_rect(self): - """Fix rects to compensate for scene offset""" - # Offset logic is to always work with copies, to avoid - # flying effects from multiple calls to _fix_rect - # See footwork in draw - if hasattr(self.rect, 'collidepoint'): - self.rect = self.rect.move(self.scene.OFFSET) - else: - self.rect = [x.move(self.scene.OFFSET) for x in self.rect] - - def should_add(self): - return True - - def set_scene(self, scene): - assert self.scene is None - self.scene = scene - if self.folder is None: - self.folder = scene.FOLDER - self.game = scene.game - for interact in self.interacts.itervalues(): - interact.set_thing(self) - self.set_interact() - - def set_interact(self): - return self._set_interact(self.select_interact()) - - def _set_interact(self, name): - self.current_interact = self.interacts[name] - self.rect = self.current_interact.interact_rect - if self.scene: - self._fix_rect() - assert self.rect is not None, name - - def select_interact(self): - return self.INITIAL - - def contains(self, pos): - if hasattr(self.rect, 'collidepoint'): - return self.rect.collidepoint(pos) - else: - for rect in list(self.rect): - if rect.collidepoint(pos): - return True - return False - - def get_description(self): - return None - - def enter(self, item): - """Called when the cursor enters the Thing.""" - pass - - def leave(self): - """Called when the cursor leaves the Thing.""" - pass - - def animate(self): - return self.current_interact.animate() - - def draw(self, surface): - old_rect = self.current_interact.rect - if old_rect: - self.current_interact.rect = old_rect.move(self.scene.OFFSET) - self.current_interact.draw(surface) - self.current_interact.rect = old_rect - if self.game.debug_rects and self._interact_hilight_color: - if hasattr(self.rect, 'collidepoint'): - draw_rect_image(surface, self._interact_hilight_color, - self.rect.inflate(1, 1), 1) - else: - for rect in self.rect: - draw_rect_image(surface, self._interact_hilight_color, - rect.inflate(1, 1), 1) - - -class ItemFactory(StatefulGizmo): - INITIAL_DATA = { - 'created': [], - } - - def __init__(self, item_class): - super(ItemFactory, self).__init__() - self.item_class = item_class - assert self.item_class.NAME is not None, ( - "%s has no NAME set" % (self.item_class,)) - self.state_key = self.item_class.NAME + '_factory' - self.items = {} - - def get_item(self, item_name): - assert item_name in self.get_data('created'), ( - "Object %s has not been created" % (item_name,)) - if item_name not in self.items: - item = self.item_class(item_name) - item.set_game(self.game) - self.items[item_name] = item - return self.items[item_name] - - def get_item_suffix(self): - return '' - - def create_item(self): - item_name = '%s:%s' % (self.item_class.NAME, self.get_item_suffix()) - created_list = self.get_data('created') - assert item_name not in created_list, ( - "Already created object %s" % (item_name,)) - created_list.append(item_name) - self.set_data('created', created_list) - return self.get_item(item_name) - - -class Item(GameDeveloperGizmo, InteractiveMixin): - """Base class for inventory items.""" - - # image for inventory - INVENTORY_IMAGE = None - - # Base name of item - NAME = None - - # name for interactions (i.e. def interact_with_) - TOOL_NAME = None - - # set to instance of CursorSprite - CURSOR = None - - ITEM_FACTORY = ItemFactory - - def __init__(self, name=None): - GameDeveloperGizmo.__init__(self) - self.name = self.NAME - if name is not None: - self.name = name - self.tool_name = self.NAME - if self.TOOL_NAME is not None: - self.tool_name = self.TOOL_NAME - self.inventory_image = None - - def _cache_inventory_image(self): - if not self.inventory_image: - self.inventory_image = self.resource.get_image( - 'items', self.INVENTORY_IMAGE) - - def get_inventory_image(self): - self._cache_inventory_image() - return self.inventory_image - - def get_inverse_interact(self, tool): - return getattr(tool, 'interact_with_' + self.tool_name, None) - - def is_interactive(self, tool=None): - if tool: - return True - return False - - -class ClonableItemFactory(ItemFactory): - def get_item_suffix(self): - # Works as long as we never remove anything from our 'created' list. - count = len(self.get_data('created')) - assert self.item_class.MAX_COUNT is not None - assert count <= self.item_class.MAX_COUNT - return str(count) - - -class CloneableItem(Item): - ITEM_FACTORY = ClonableItemFactory - MAX_COUNT = None diff -r f95830b58336 -r 79b5c1be9a5e pyntnclick/tests/__init__.py diff -r f95830b58336 -r 79b5c1be9a5e pyntnclick/tests/game_logic_utils.py --- a/pyntnclick/tests/game_logic_utils.py Sat Jun 21 22:04:35 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,113 +0,0 @@ -import unittest - -import pygame.display -import pygame.event - -import pyntnclick.resources -import pyntnclick.state - - -class GameLogicTestCase(unittest.TestCase): - CURRENT_SCENE = None - GAME_DESCRIPTION_CLASS = None - - def setUp(self): - # Events require us to initialize the display - pygame.display.init() - # Disable alpha conversion which requires a screen - pyntnclick.resources.Resources.CONVERT_ALPHA = False - - self.game_description = self.GAME_DESCRIPTION_CLASS() - self.state = self.game_description.initial_state() - self.scene_stack = [] - - # We aren't handling events, monkey patch change_scene and show_detail - def change_scene(name): - self.state.data.set_current_scene(name) - self.scene_stack = [self.state.get_current_scene()] - self.state.change_scene = change_scene - - def show_detail(name): - self.scene_stack.append(self.state.detail_views[name]) - self.state.show_detail = show_detail - - self.state.change_scene(self.CURRENT_SCENE) - - def close_detail(self): - self.scene_stack.pop() - self.assertTrue(len(self.scene_stack) > 0) - - def clear_event_queue(self): - # Since we aren't handling events, we may overflow the pygame - # event buffer if we're generating a lot of events - pygame.event.clear() - - def clear_inventory(self): - # Remove all items from the inventory, ensuring tool is set to None - self.state.set_tool(None) - self.state.inventory()[:] = [] - - def set_game_data(self, key, value, thing=None): - gizmo = self.state.get_current_scene() - if thing is not None: - gizmo = gizmo.things[thing] - gizmo.set_data(key, value) - - def assert_game_data(self, key, value, thing=None, scene=None, - detail=None): - gizmo = self.state.get_current_scene() - if scene is not None: - gizmo = self.state.scenes[scene] - if detail is not None: - gizmo = self.state.detail_views[detail] - if thing is not None: - gizmo = gizmo.things[thing] - self.assertEquals(value, gizmo.get_data(key)) - - def assert_inventory_item(self, item, in_inventory=True): - self.assertEquals(in_inventory, self.state.is_in_inventory(item)) - - def assert_scene_thing(self, thing, in_scene=True): - self.assertEquals( - in_scene, thing in self.state.get_current_scene().things) - - def assert_detail_thing(self, thing, in_detail=True): - self.assertEquals(in_detail, thing in self.scene_stack[-1].things) - - def assert_item_exists(self, item, exists=True): - try: - self.state.get_item(item) - self.assertTrue(exists) - except: - self.assertFalse(exists) - - def assert_current_scene(self, scene): - self.assertEquals(scene, self.state.get_current_scene().name) - - def handle_result(self, result): - self.clear_event_queue() - if result is None: - return None - if hasattr(result, 'process'): - if result.detail_view: - self.state.show_detail(result.detail_view) - return result - return [self.handle_result(r) for r in result] - - def interact_thing(self, thing, item=None, detail=None): - item_obj = None - if item is not None: - self.assert_inventory_item(item) - item_obj = self.state.get_item(item) - thing_container = self.scene_stack[-1] - if detail is not None: - self.assertEqual(detail, thing_container.name) - result = thing_container.things[thing].interact(item_obj) - return self.handle_result(result) - - def interact_item(self, target_item, item): - self.assert_inventory_item(target_item) - item_obj = self.state.get_item(item) - target_obj = self.state.get_item(target_item) - result = target_obj.interact(item_obj) - return self.handle_result(result) diff -r f95830b58336 -r 79b5c1be9a5e pyntnclick/tests/mad_clicker.py --- a/pyntnclick/tests/mad_clicker.py Sat Jun 21 22:04:35 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,106 +0,0 @@ -from pyntnclick.tests.game_logic_utils import GameLogicTestCase - - -class MadClickerTestCase(GameLogicTestCase): - "Provide a 'mad clicker' test to expose potential undefined behaviour" - - def check_result_obj(self, obj): - """Check that the obj is the sort of result obj/seq we expect""" - if obj is None: - return True - if hasattr(obj, 'process'): - return True - return False - - def check_result(self, obj): - """Check that the obj is the sort of result obj/seq we expect""" - # We do it this way, because we don't allow seqs to contain seqs - if not self.check_result_obj(obj): - for subobj in obj: - if not self.check_result_obj(subobj): - return False - return True - - def _format_item(self, item): - return "%s (%s)" % (item.name, item) - - def _format_thing(self, thing): - if not hasattr(thing, 'current_interact'): - return self._format_item(thing) - interact_name = None - if thing.current_interact and thing.interacts: - for name in thing.interacts: - if thing.interacts[name] == thing.current_interact: - interact_name = name - break - if interact_name: - return "%s:%s (%s %s)" % (thing.name, interact_name, - thing, thing.current_interact) - elif thing.current_interact: - return "%s: (%s %s)" % (thing.name, thing, - thing.current_interact) - else: - return "%s: (%s %s)" % (thing.name, thing) - - def format_error(self, thing, item, exception): - if not item: - msg = ("Unexpected result for interact with no item for %s" - % self._format_thing(thing)) - else: - msg = ("Unexpected result for interact with item %s with %s" % - (self._format_item(item), self._format_thing(thing))) - if exception: - return "Exception raised %s:\nTest failed: %s" % (exception, msg) - return msg - - def do_thing(self, thing, item): - try: - if item: - # We're interacting with an item in the inventory - self.state.add_inventory_item(item.name) - self.assertEqual(self.check_result(thing.interact(item)), True, - self.format_error(thing, item, None)) - except self.failureException: - raise - except Exception, details: - raise self.failureException(self.format_error(thing, item, - details)) - self.clear_inventory() - self.clear_event_queue() - - def do_item(self, item, item2): - try: - self.state.add_inventory_item(item.name) - if item2: - self.state.add_inventory_item(item2.name) - self.assertEqual(self.check_result(item.interact(item2)), True, - self.format_error(item, item2, None)) - except self.failureException: - raise - except Exception, details: - raise self.failureException(self.format_error(item, item2, - details)) - self.clear_inventory() - self.clear_event_queue() - - def do_mad_clicker(self): - """Implement frantic clicking behaviour""" - for scene in self.state.scenes.values(): - self.state.data.set_current_scene(scene.name) - for thing in scene.things.values(): - for interact_name in thing.interacts: - thing._set_interact(interact_name) - self.do_thing(thing, None) - for item in self.state.items.values(): - self.do_thing(thing, item) - for detail in self.state.detail_views.values(): - for thing in detail.things.values(): - for interact_name in thing.interacts: - thing._set_interact(interact_name) - self.do_thing(thing, None) - for item in self.state.items.values(): - self.do_thing(thing, item) - for item in self.state.items.values(): - self.do_item(item, None) - for item2 in self.state.items.values(): - self.do_item(item, item2) diff -r f95830b58336 -r 79b5c1be9a5e pyntnclick/tests/test_resources.py --- a/pyntnclick/tests/test_resources.py Sat Jun 21 22:04:35 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,75 +0,0 @@ -import os.path -from unittest import TestCase - -from pygame.surface import Surface - -from pyntnclick.resources import Resources, ResourceNotFound - - -TEST_PATH = os.path.dirname(__file__) -DATA_PATH = os.path.join(os.path.dirname(TEST_PATH), 'data') - -test_path = lambda p: os.path.join(TEST_PATH, p) -data_path = lambda p: os.path.join(DATA_PATH, p) - - -class ResourcesTestCase(TestCase): - def setUp(self): - self.res = self.get_resource_loader() - - def get_resource_loader(self, *args, **kw): - res = Resources('pyntnclick.tests', *args, **kw) - res.CONVERT_ALPHA = False # Because we have no display. - return res - - def test_get_paths_no_lang(self): - self.assertEqual([test_path('thing'), data_path('thing')], - self.res.get_paths('thing')) - - def test_get_paths_lang(self): - res = self.get_resource_loader('en') - self.assertEqual([test_path('en/thing'), test_path('thing'), - data_path('en/thing'), data_path('thing')], - res.get_paths('thing')) - - def test_get_paths_lang_dialect(self): - res = self.get_resource_loader('en_ZA') - self.assertEqual([test_path('en_ZA/thing'), test_path('en/thing'), - test_path('thing'), data_path('en_ZA/thing'), - data_path('en/thing'), data_path('thing')], - res.get_paths('thing')) - - def test_get_resource_path_missing(self): - try: - self.res.get_resource_path('should_not_exist') - self.fail('Expected ResourceNotFound error.') - except ResourceNotFound, e: - self.assertEqual('should_not_exist', e.args[0]) - - def test_get_resource_path_in_test(self): - self.assertEqual(test_path('test_resources.py'), - self.res.get_resource_path('test_resources.py')) - - def test_get_resource_path_in_data(self): - self.assertEqual( - data_path('images/pyntnclick/hand.png'), - self.res.get_resource_path('images/pyntnclick/hand.png')) - - def test_get_image(self): - image = self.res.get_image('pyntnclick/hand.png') - self.assertTrue(isinstance(image, Surface)) - - def test_get_image_fragments(self): - image = self.res.get_image('pyntnclick', 'hand.png') - self.assertTrue(isinstance(image, Surface)) - - def test_get_image_different_basedir(self): - image = self.res.get_image('hand.png', basedir='images/pyntnclick') - self.assertTrue(isinstance(image, Surface)) - - def test_load_missing(self): - try: - self.res.get_image('should_not_exist') - self.fail('Expected ResourceNotFound error.') - except ResourceNotFound, e: - self.assertEqual('images/should_not_exist', e.args[0]) diff -r f95830b58336 -r 79b5c1be9a5e pyntnclick/tools/__init__.py diff -r f95830b58336 -r 79b5c1be9a5e pyntnclick/tools/gen_sound.py --- a/pyntnclick/tools/gen_sound.py Sat Jun 21 22:04:35 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,57 +0,0 @@ -# Generate 'perfect' sine wave sounds - -# Design notes: produces ~= (use requested) s raw CDDA audio - 44100 Hz -# 16 bit signed values -# Input is freq in Hz - 440 for A, etc. - must be an integer -# Output is written the file beep.pcm -# Convert to ogg with oggenc -r - -import sys -import math -import struct - -CDDA_RATE = 44100 -MAX = 105 * 256 # Max value for sine wave - - -def gen_sine(freq, secs): - filename = 'beep%s.pcm' % freq - # We need to generate freq cycles and sample that CDDA_RATE times - samples_per_cycle = CDDA_RATE / freq - data = [] - for x in range(samples_per_cycle): - rad = float(x) / samples_per_cycle * 2 * math.pi - y = MAX * math.sin(rad) - data.append(struct.pack(' []' - print ' where is the frequency in Hz (integer)' - print ' and [] is the time is seconds (float)' - -if __name__ == "__main__": - try: - freq = int(sys.argv[1]) - except Exception, exc: - usage() - print 'Error was: %s' % exc - sys.exit(1) - - if len(sys.argv) > 2: - try: - secs = float(sys.argv[2]) - except Exception, exc: - usage() - print 'Error was: %s' % exc - sys.exit(1) - else: - secs = 0.25 - output = gen_sine(freq, secs) - print 'Wrote sample to %s' % output diff -r f95830b58336 -r 79b5c1be9a5e pyntnclick/tools/rect_drawer.py --- a/pyntnclick/tools/rect_drawer.py Sat Jun 21 22:04:35 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,743 +0,0 @@ -# Quickly hacked together helper for working out -# interactive regions in Suspended Sentence - -from pygame.locals import (K_LEFT, K_RIGHT, K_UP, K_DOWN, - K_a, K_t, K_d, K_i, K_r, K_o, K_b, K_z, - QUIT, MOUSEBUTTONDOWN, MOUSEMOTION, - MOUSEBUTTONUP, KEYDOWN) -import pygame -import os - -import pyntnclick.constants -from pyntnclick.i18n import _ -from pyntnclick.widgets.text import LabelWidget, TextButton -from pyntnclick.widgets.base import Container, Button, TranslucentImage -from pyntnclick.widgets.filechooser import FileChooser -from pyntnclick.utils import draw_rect_image - -DRAW, CYCLE, DELETE, IMAGE = range(4) - - -class RectDrawerError(Exception): - """Raised when initilaization failed""" - - -class RectDrawerConstants(pyntnclick.constants.GameConstants): - debug = True - menu_width = 200 - menu_button_height = 25 - zoom = 4 - zoom_step = 100 - -constants = RectDrawerConstants() - - -class ColourButton(Button): - """Button for selecting a colour""" - - sel_colour = pygame.color.Color(255, 255, 255) - unsel_colour = pygame.color.Color(128, 128, 128) - - padding = 2 - border = 3 - - def __init__(self, rect, gd, colour, palette, size=None): - super(ColourButton, self).__init__(rect, gd, size=size) - self._colour = pygame.color.Color(colour) - self._button_rect = self.rect.inflate(-self.padding, -self.padding) - self._colour_rect = self._button_rect.inflate(-self.border, - -self.border) - self.selected = False - self._palette = palette - self.add_callback('clicked', self.fix_selection) - - def fix_selection(self, ev, widget): - self._palette.cur_selection.selected = False - self.selected = True - self._palette.cur_selection = self - - def draw(self, surface): - if self.visible: - self.do_prepare() - surface.fill(pygame.color.Color(0, 0, 0), self.rect) - if self.selected: - surface.fill(self.sel_colour, self._button_rect) - else: - surface.fill(self.unsel_colour, self._button_rect) - surface.fill(self._colour, self._colour_rect) - - -class AppPalette(Container): - - but_size = 35 - - colors = [ - 'red', 'maroon1', 'palevioletred1', 'moccasin', 'orange', - 'honeydew', 'yellow', 'gold', 'goldenrod', 'brown', - 'blue', 'purple', 'darkorchid4', 'thistle', 'skyblue1', - 'green', 'palegreen1', 'darkgreen', 'aquamarine', 'darkolivegreen', - ] - - def __init__(self, pos, gd, app_image, size=None): - self.image = app_image - super(AppPalette, self).__init__(pos, gd, size=size) - self.selection = 0 - self.image.rect_color = pygame.color.Color(self.colors[self.selection]) - - x, y = pos - for num, col in enumerate(self.colors): - if (x - self.rect.left + self.but_size) >= self.rect.width: - x = self.rect.left - y += self.but_size - button = ColourButton((x, y), - gd, col, self, size=(self.but_size, self.but_size)) - x += self.but_size - if num == 0: - self.cur_selection = button - button.fix_selection(None, None) - button.add_callback('clicked', self.click_item, num) - self.add(button) - # Fix height - self.rect.height = y + self.but_size - self.rect.top - - def click_item(self, ev, widget, number): - self.selection = number - self.image.rect_color = pygame.color.Color(self.colors[number]) - - -class AppImage(Container): - - rect_thick = 3 - draw_thick = 1 - - def __init__(self, parent, gd, state, scene, detail): - self.state = state - super(AppImage, self).__init__((0, 0), gd, size=constants.screen) - self.mode = DRAW - self._scene = scene - self._parent = parent - self._detail = detail - self.rects = [] - self.images = [] - self.start_pos = None - self.end_pos = None - self.rect_color = pygame.color.Color('white') - self.current_image = None - self.place_image_menu = None - self.close_button = LabelWidget((0, 0), gd, _('Close')) - self.close_button.fg_color = (0, 0, 0) - self.close_button.bg_color = (0, 0, 0) - self.draw_rects = True - self.draw_things = True - self.draw_thing_rects = True - self.draw_images = True - self.trans_images = False - self.draw_toolbar = True - self.old_mouse_pos = None - self.zoom_display = False - self.draw_anim = False - self.zoom_offset = (600, 600) - self.clear_display = False - self.filechooser = None - if self._detail: - w, h = self._scene.get_detail_size() - rect = pygame.rect.Rect(0, 0, w, h) - self.close_button.rect.midbottom = rect.midbottom - self.offset = (0, 0) - else: - self.offset = (-self._scene.OFFSET[0], - -self._scene.OFFSET[1]) - self.find_existing_intersects() - self.add_callback(MOUSEBUTTONDOWN, self.mouse_down) - self.add_callback(MOUSEBUTTONUP, self.mouse_up) - self.add_callback(MOUSEMOTION, self.mouse_move) - self.add_callback(KEYDOWN, self.key_down) - - def get_mode_name(self): - """Return the translated mode name""" - # We do things this way to avoid defining translated strings - # at import time - if self.mode == DRAW: - return _("draw") - elif self.mode == CYCLE: - return _("cycle") - elif self.mode == DELETE: - return _("delete") - elif self.mode == IMAGE: - return _("image") - else: - raise RuntimeError("Invalid mode") - - def _print_thing(self, thing, interact_name): - """Helper to avoid repeated translations""" - print (_("Thing %(thing)s Interact %(interact)s") % - {'thing': thing.name, 'interact': interact_name}) - - def _print_rects(self, rect1, rect2): - """Helper to avoid repeated translations""" - print _(" Rects"), rect1, rect2 - - def find_existing_intersects(self): - """Parse the things in the scene for overlaps""" - scene = self._scene - # Pylint hates this function - for thing in scene.things.itervalues(): - for interact_name in thing.interacts: - thing._set_interact(interact_name) - if hasattr(thing.rect, 'collidepoint'): - thing_rects = [thing.rect] - else: - thing_rects = thing.rect - for thing2 in scene.things.itervalues(): - if thing is thing2: - continue - for interact2_name in thing2.interacts: - thing2._set_interact(interact2_name) - if hasattr(thing2.rect, 'collidepoint'): - thing2_rects = [thing2.rect] - else: - thing2_rects = thing2.rect - for my_rect in thing_rects: - for other_rect in thing2_rects: - if my_rect.colliderect(other_rect): - print _('Existing Intersecting rects') - self._print_thing(thing, interact_name) - self._print_thing(thing2, interact2_name) - self._print_rects(my_rect, other_rect) - print - - def find_intersecting_rects(self, d): - """Find if any rect collections intersect""" - # I loath N^X brute search algorithms, but whatever, hey - scene = self._scene - for (num, col) in enumerate(d): - rect_list = d[col] - for thing in scene.things.itervalues(): - for interact_name in thing.interacts: - thing._set_interact(interact_name) - if hasattr(thing.rect, 'collidepoint'): - thing_rects = [thing.rect] - else: - thing_rects = thing.rect - for other_rect in thing_rects: - for my_rect in rect_list: - if my_rect.colliderect(other_rect): - print _('Intersecting rects') - print _(" Object %s") % num - self._print_thing(thing, interact_name) - self._print_rects(my_rect, other_rect) - if thing.INITIAL: - thing._set_interact(thing.INITIAL) - print - for (num2, col2) in enumerate(d): - if num2 == num: - continue - other_list = d[col2] - for my_rect in rect_list: - for other_rect in other_list: - if my_rect.colliderect(other_rect): - print _('Intersecting rects'), - print (_(' Object %(object1)s and %(object2)s') % - {'object1': num, 'object2': num2}) - self._print_rects(my_rect, other_rect) - print - print - - def toggle_things(self, ev, widget): - self.draw_things = not self.draw_things - - def toggle_thing_rects(self, ev, widget): - self.draw_thing_rects = not self.draw_thing_rects - scene = self._scene - for thing in scene.things.itervalues(): - if not self.draw_thing_rects: - if not hasattr(thing, 'old_colour'): - thing.old_colour = thing._interact_hilight_color - thing._interact_hilight_color = None - else: - thing._interact_hilight_color = thing.old_colour - - def toggle_images(self, ev, widget): - self.draw_images = not self.draw_images - for image in self.images: - image.set_visible(self.draw_images) - if self.current_image: - self.current_image.set_visible(self.draw_images) - self.invalidate() - - def toggle_trans_images(self, ev, widget): - self.trans_images = not self.trans_images - for image in self.images: - image.translucent = self.trans_images - if self.current_image: - self.current_image.translucent = self.trans_images - self.invalidate() - - def toggle_rects(self, ev, widget): - self.draw_rects = not self.draw_rects - - def toggle_toolbar(self, ev, widget): - self.draw_toolbar = not self.draw_toolbar - - def toggle_zoom(self, ev, widget): - self.zoom_display = not self.zoom_display - self.invalidate() - - def toggle_anim(self, ev, widget): - self.draw_anim = not self.draw_anim - - def draw_mode(self, ev, widget): - self.mode = DRAW - - def del_mode(self, ev, widget): - self.mode = DELETE - self.start_pos = None - self.end_pos = None - - def invalidate(self): - self.clear_display = True - - def draw(self, surface): - if not self.visible: - return - self.do_prepare() - if self.clear_display: - surface.fill(pygame.color.Color(0, 0, 0), - pygame.Rect(0, 0, constants.screen[0], - constants.screen[1])) - self.clear_display = False - - if self.zoom_display: - base_surface = surface.copy() - self.do_unzoomed_draw(base_surface) - zoomed = pygame.transform.scale(base_surface, - (constants.zoom * base_surface.get_width(), - constants.zoom * base_surface.get_height())) - area = pygame.rect.Rect(self.zoom_offset[0], self.zoom_offset[1], - constants.screen[0], constants.screen[1]) - surface.blit(zoomed, (0, 0), area) - else: - self.do_unzoomed_draw(surface) - - def do_unzoomed_draw(self, surface): - if self.draw_things: - self._scene.draw(surface) - else: - self._scene.draw_background(surface) - if self._detail: - # We duplicate draw logic here, so we zoom the close - # button correctly - self.close_button.draw(surface) - if self.mode == DRAW and self.start_pos and self.draw_rects: - rect = pygame.rect.Rect(self.start_pos[0], self.start_pos[1], - self.end_pos[0] - self.start_pos[0], - self.end_pos[1] - self.start_pos[1]) - rect.normalize() - draw_rect_image(surface, self.rect_color, rect, self.draw_thick) - if self.draw_rects: - for (col, rect) in self.rects: - draw_rect_image(surface, col, rect, self.rect_thick) - for image in self.images: - image.draw(surface) - if self.current_image and self.mode == IMAGE: - self.current_image.draw(surface) - if self.draw_toolbar: - tb_surf = surface.subsurface(0, constants.screen[1] - - constants.button_size, - constants.screen[0], - constants.button_size).convert_alpha() - tb_surf.fill(pygame.color.Color(127, 0, 0, 191)) - surface.blit(tb_surf, (0, constants.screen[1] - - constants.button_size)) - - def _make_dict(self): - d = {} - for col, rect in self.rects: - col = (col.r, col.g, col.b) - d.setdefault(col, []) - d[col].append(rect) - return d - - def print_objs(self, ev, widget): - d = self._make_dict() - self.find_intersecting_rects(d) - for (num, col) in enumerate(d): - print _('Rect %d : ') % num - for rect in d[col]: - r = rect.move(self.offset) - print ' (%d, %d, %d, %d),' % (r.x, r.y, r.w, r.h) - print - for i, image in enumerate(self.images): - print _('Image %d') % i - rect = image.rect - r = rect.move(self.offset) - print ' (%d, %d, %d, %d),' % (r.x, r.y, r.w, r.h) - print - print - - def image_load(self, ev, widget): - if self.filechooser is None: - self.filechooser = FileChooser((0, 0), self.gd, None, os.curdir, - self.do_load_image) - else: - self.filechooser.refresh() - self.invalidate() - self._parent.paused = True - self._parent.add(self.filechooser) - - def do_load_image(self, filename): - try: - self.current_image = TranslucentImage((0, 0), self.gd, - pygame.image.load(filename)) - if not self.draw_images: - # Selecting an image makes image visible - self.toggle_images(None, None) - self.current_image.translucent = self.trans_images - self.place_image_menu.enabled = True - self.current_image.rect = self.current_image.rect.move( - constants.screen[0] + constants.menu_width, - constants.screen[1]) - self.image_mode(None, None) - except pygame.error, e: - print 'Unable to load image %s (reason %s)' % (filename, e) - - def image_mode(self, ev, widget): - self.mode = IMAGE - self.start_pos = None - self.end_pos = None - # So we do the right thing for off screen images - self.old_mouse_pos = None - - def cycle_mode(self, ev, widget): - self.mode = CYCLE - - def _conv_pos(self, mouse_pos): - if self.zoom_display: - pos = ((mouse_pos[0] + self.zoom_offset[0]) / constants.zoom, - (mouse_pos[1] + self.zoom_offset[1]) / constants.zoom) - else: - pos = mouse_pos - return pos - - def _check_limits(self, offset): - if offset[0] < 0: - offset[0] = 0 - if offset[1] < 0: - offset[1] = 0 - width, height = constants.screen - if offset[0] > constants.zoom * width - width: - offset[0] = constants.zoom * width - width - if offset[1] > constants.zoom * height - height: - offset[1] = constants.zoom * height - height - - def _make_zoom_offset(self, pos): - zoom_pos = (pos[0] * constants.zoom, pos[1] * constants.zoom) - offset = [zoom_pos[0] - constants.screen[0] / 2, - zoom_pos[1] - constants.screen[1] / 2] - self._check_limits(offset) - self.zoom_offset = tuple(offset) - - def _move_zoom(self, x, y): - offset = list(self.zoom_offset) - offset[0] += constants.zoom_step * x - offset[1] += constants.zoom_step * y - self._check_limits(offset) - self.zoom_offset = tuple(offset) - - def key_down(self, ev, widget): - if self.mode == IMAGE and self.current_image: - # Move the image by 1 pixel - cur_pos = self.current_image.rect.center - if ev.key == K_LEFT: - self.current_image.rect.center = (cur_pos[0] - 1, cur_pos[1]) - elif ev.key == K_RIGHT: - self.current_image.rect.center = (cur_pos[0] + 1, cur_pos[1]) - elif ev.key == K_UP: - self.current_image.rect.center = (cur_pos[0], cur_pos[1] - 1) - elif ev.key == K_DOWN: - self.current_image.rect.center = (cur_pos[0], cur_pos[1] + 1) - elif self.zoom_display: - if ev.key == K_LEFT: - self._move_zoom(-1, 0) - elif ev.key == K_RIGHT: - self._move_zoom(1, 0) - elif ev.key == K_UP: - self._move_zoom(0, -1) - elif ev.key == K_DOWN: - self._move_zoom(0, 1) - - if ev.key == K_o: - self.toggle_trans_images(None, None) - elif ev.key == K_t: - self.toggle_things(None, None) - elif ev.key == K_r: - self.toggle_thing_rects(None, None) - elif ev.key == K_i: - self.toggle_images(None, None) - elif ev.key == K_d: - self.toggle_rects(None, None) - elif ev.key == K_b: - self.toggle_toolbar(None, None) - elif ev.key == K_z: - self.toggle_zoom(None, None) - elif ev.key == K_a: - self.toggle_anim(None, None) - - def mouse_down(self, ev, widget): - pos = self._conv_pos(ev.pos) - if self._parent.paused: - # Ignore this if the filechooser is active - return False - if self.mode == DELETE: - cand = None - # Images are drawn above rectangles, so search those first - for image in self.images: - if image.rect.collidepoint(pos): - cand = image - break - if cand: - self.images.remove(cand) - self.invalidate() - return - for (col, rect) in self.rects: - if rect.collidepoint(pos): - cand = (col, rect) - break - if cand: - self.rects.remove(cand) - self.invalidate() - elif self.mode == CYCLE: - scene = self._scene - cand = None - for thing in scene.things.itervalues(): - if thing.contains(pos): - cand = thing - break - if cand: - # Find current interacts in this thing - cur_interact = cand.current_interact - j = cand.interacts.values().index(cur_interact) - if j + 1 < len(cand.interacts): - next_name = cand.interacts.keys()[j + 1] - else: - next_name = cand.interacts.keys()[0] - if cand.interacts[next_name] != cur_interact: - cand._set_interact(next_name) - elif self.mode == DRAW: - self.start_pos = pos - self.end_pos = pos - elif self.mode == IMAGE: - if self.current_image: - self.images.append(self.current_image) - self.current_image = None - self.old_mouse_pos = None - self.invalidate() - else: - cand = None - for image in self.images: - if image.rect.collidepoint(pos): - cand = image - break - if cand: - self.images.remove(cand) - self.current_image = cand - # We want to move relative to the current mouse pos, so - self.old_mouse_pos = pos - self.invalidate() - - def mouse_up(self, ev, widget): - if self._parent.paused: - return False - if self.mode == DRAW: - if self.start_pos is None: - # We've come here not via a drawing situation, so bail - return False - rect = pygame.rect.Rect(self.start_pos[0], self.start_pos[1], - self.end_pos[0] - self.start_pos[0], - self.end_pos[1] - self.start_pos[1]) - rect.normalize() - self.rects.append((self.rect_color, rect)) - self.start_pos = self.end_pos = None - - def mouse_move(self, ev, widget): - # We're only interested in this if left mouse button is down or we've - # got and image - if self.mode == IMAGE and self.current_image: - pos = self._conv_pos(ev.pos) - if self.old_mouse_pos: - delta = (pos[0] - self.old_mouse_pos[0], - pos[1] - self.old_mouse_pos[1]) - self.current_image.rect.center = ( - self.current_image.rect.center[0] + delta[0], - self.current_image.rect.center[1] + delta[1]) - else: - self.current_image.rect.center = pos - self.invalidate() - self.old_mouse_pos = pos - return True - elif ev.buttons[0] == 1 and self.mode == DRAW: - self.end_pos = self._conv_pos(ev.pos) - return True - return False - - def animate(self): - if self.draw_anim: - self._scene.animate() - - -class ModeLabel(LabelWidget): - - def __init__(self, pos, gd, app_image, size=None): - self.app_image = app_image - super(ModeLabel, self).__init__(pos, - gd, _('Mode : '), fontname=constants.bold_font, - fontsize=15, color=pygame.color.Color(128, 0, 255), - size=size) - self.start_rect = self.rect.copy() - - def draw(self, surface): - self.do_prepare() - text = _('Mode : %s') % self.app_image.get_mode_name() - if self.text != text: - self.text = text - self.is_prepared = False - self.rect = self.start_rect.copy() - self.do_prepare() - super(ModeLabel, self).draw(surface) - - -def make_button(text, gd, action, ypos): - rect = pygame.rect.Rect(0, 0, constants.menu_width, - constants.menu_button_height) - rect.move_ip(805, ypos) - button = TextButton(rect.topleft, gd, text, size=(constants.menu_width, - constants.menu_button_height), - fontname=constants.font, fontsize=12, - color=pygame.color.Color(255, 255, 0), border=1, padding=3) - button.add_callback('clicked', action) - return button - - -class RectApp(Container): - """The actual rect drawer main app""" - def __init__(self, rect, gd, detail): - super(RectApp, self).__init__(rect, gd) - - try: - state = gd.initial_state() - scene = state.scenes[gd._initial_scene] - except KeyError: - raise RectDrawerError(_('Invalid scene: %s') % gd._initial_scene) - gd.sound.disable_sound() # No sound here - - if detail: - try: - scene = state.detail_views[detail] - except KeyError: - raise RectDrawerError(_('Invalid detail: %s') % detail) - - self.paused = False - - self.image = AppImage(self, gd, state, scene, detail is not None) - self.add(self.image) - mode_label = ModeLabel((805, 0), self.gd, self.image, size=(200, 50)) - self.add(mode_label) - y = mode_label.rect.height - draw = make_button(_('Draw Rect'), gd, self.image.draw_mode, y) - self.add(draw) - y += draw.rect.height - load_image = make_button(_("Load image"), gd, self.image.image_load, y) - self.add(load_image) - y += load_image.rect.height - add_image = make_button(_("Place/Move images"), gd, - self.image.image_mode, y) - add_image.enabled = False - self.add(add_image) - self.image.place_image_menu = add_image - y += add_image.rect.height - cycle = make_button(_("Cycle interacts"), gd, self.image.cycle_mode, y) - self.add(cycle) - y += cycle.rect.height - delete = make_button(_("Delete Objects"), gd, self.image.del_mode, y) - self.add(delete) - y += delete.rect.height - palette = AppPalette((810, y), gd, self.image, size=(200, 0)) - self.add(palette) - y += palette.rect.height - print_rects = make_button(_("Print objects"), gd, - self.image.print_objs, y) - self.add(print_rects) - y += print_rects.rect.height - toggle_things = make_button(_("Show Things (t)"), gd, - self.image.toggle_things, y) - self.add(toggle_things) - y += toggle_things.rect.height - toggle_thing_rects = make_button(_("Show Thing Rects (r)"), gd, - self.image.toggle_thing_rects, y) - self.add(toggle_thing_rects) - y += toggle_thing_rects.rect.height - toggle_images = make_button(_("Show Images (i)"), gd, - self.image.toggle_images, y) - self.add(toggle_images) - y += toggle_images.rect.height - trans_images = make_button(_("Opaque Images (o)"), gd, - self.image.toggle_trans_images, y) - self.add(trans_images) - y += trans_images.rect.height - toggle_rects = make_button(_("Show Drawn Rects (d)"), gd, - self.image.toggle_rects, y) - self.add(toggle_rects) - y += toggle_rects.rect.height - toggle_toolbar = make_button(_("Show Toolbar (b)"), gd, - self.image.toggle_toolbar, y) - self.add(toggle_toolbar) - y += toggle_toolbar.rect.height - toggle_anim = make_button(_("Show Animations (a)"), gd, - self.image.toggle_anim, y) - self.add(toggle_anim) - y += toggle_anim.rect.height - toggle_zoom = make_button(_("Zoom (z)"), gd, - self.image.toggle_zoom, y) - self.add(toggle_zoom) - y += toggle_zoom.rect.height - quit_but = make_button(_("Quit"), gd, self.quit, 570) - self.add(quit_but) - - def quit(self, ev, widget): - pygame.event.post(pygame.event.Event(QUIT)) - - def animate(self): - self.image.animate() - - -class RectEngine(object): - """Engine for the rect drawer.""" - - def __init__(self, gd, detail): - self.state = None - self._gd = gd - rect = pygame.display.get_surface().get_rect() - self.app = RectApp(rect.topleft, self._gd, detail) - - def run(self): - """App loop""" - clock = pygame.time.Clock() - while True: - events = pygame.event.get() - for ev in events: - if ev.type == QUIT: - return - else: - self.app.event(ev) - self.app.animate() - surface = pygame.display.get_surface() - self.app.draw(surface) - pygame.display.flip() - clock.tick(self._gd.constants.frame_rate) - - -def make_rect_display(): - pygame.display.init() - pygame.font.init() - pygame.display.set_mode((constants.screen[0] - + constants.menu_width, constants.screen[1])) diff -r f95830b58336 -r 79b5c1be9a5e pyntnclick/utils.py --- a/pyntnclick/utils.py Sat Jun 21 22:04:35 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,73 +0,0 @@ -# Misc utils I don't know where else to put - -import pygame -from pygame.locals import SRCALPHA -from pygame.surface import Surface - - -def list_scenes(scene_module, scene_list): - """List the scenes in the state""" - print "Available scenes and details:" - for scene in scene_list: - scenemod = __import__('%s.%s' % (scene_module, scene), - fromlist=[scene]) - if scenemod.SCENES: - print " * %s" % scene - else: - print " * %s (details only)" % scene - for detailcls in getattr(scenemod, 'DETAIL_VIEWS', []): - print " - %s" % detailcls.NAME - - -def draw_rect_image(surface, color, rect, thickness): - """Draw a rectangle with lines thickness wide""" - # top - surface.fill(color, (rect.left, rect.top, rect.width, thickness)) - # bottom - surface.fill(color, (rect.left, rect.bottom - thickness, rect.width, - thickness)) - # left - surface.fill(color, (rect.left, rect.top, thickness, rect.height)) - # right - surface.fill(color, (rect.right - thickness, rect.top, thickness, - rect.height)) - - -def convert_color(color): - """Give me a pygame Color, dammit""" - if isinstance(color, pygame.Color): - return color - if isinstance(color, basestring): - return pygame.Color(color) - return pygame.Color(*color) - - -def render_text(text, fontname, font_size, color, bg_color, resource, size, - centre=True): - """Render the text so it will fit in the given size, reducing font - size as needed. - - Note that this does not do any text wrapping.""" - done = False - width, height = size - color = convert_color(color) - bg_color = convert_color(bg_color) - surface = Surface(size, SRCALPHA).convert_alpha() - surface.fill(bg_color) - while not done and font_size > 0: - # We bail at font_size 1 and just clip in that case, since we're - # out of good options - font = resource.get_font(fontname, font_size) - text_surf = font.render(text, True, color) - if (text_surf.get_width() > width or text_surf.get_height() > height): - font_size -= 1 - else: - done = True - if centre: - # Centre the text in the rect - x = max(0, (width - text_surf.get_width()) / 2) - y = max(0, (height - text_surf.get_height()) / 2) - else: - x = y = 0 - surface.blit(text_surf, (x, y)) - return surface diff -r f95830b58336 -r 79b5c1be9a5e pyntnclick/version.py --- a/pyntnclick/version.py Sat Jun 21 22:04:35 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,82 +0,0 @@ -"""Pyntnclick Version Information""" - -VERSION = (0, 0, 1, 'alpha', 0) -BASE_VERSION_STR = '.'.join([str(x) for x in VERSION[:3]]) -VERSION_STR = { - 'final': BASE_VERSION_STR, - 'alpha': BASE_VERSION_STR + 'a' + str(VERSION[4]), - 'rc': BASE_VERSION_STR + 'rc' + str(VERSION[4]), -}[VERSION[3]] - -NAME = 'Pyntnclick' -DESCRIPTION = 'Point-and-click adventure game engine using Pygame.' - -PEOPLE = { - 'Simon': ('Simon Cross', 'hodgestar+rinkhals@gmail.com'), - 'Neil': ('Neil Muller', 'drnmuller+rinkhals@gmail.com'), - 'Adrianna': ('Adrianna Pinska', 'adrianna.pinska+rinkhals@gmail.com'), - 'Jeremy': ('Jeremy Thurgood', 'firxen+rinkhals@gmail.com'), - 'Stefano': ('Stefano Rivera', 'stefano@rivera.za.net'), -} - -AUTHORS = [ - PEOPLE['Simon'], - PEOPLE['Neil'], - PEOPLE['Adrianna'], - PEOPLE['Jeremy'], - PEOPLE['Stefano'], -] - -AUTHOR_NAME = AUTHORS[0][0] -AUTHOR_EMAIL = AUTHORS[0][1] - -MAINTAINERS = AUTHORS - -MAINTAINER_NAME = MAINTAINERS[0][0] -MAINTAINER_EMAIL = MAINTAINERS[0][1] - -ARTISTS = [ - PEOPLE['Adrianna'], -] - -DOCUMENTERS = [ - PEOPLE['Simon'], - PEOPLE['Stefano'], -] - -# HOMEPAGE = 'http://sourceforge.net/projects/XXXX/' -# PYPI_URL = 'http://pypi.python.org/pypi/XXXX/' - -LICENSE = 'MIT' -# LICENSE_TEXT = resource_string(__name__, 'COPYING') - -CLASSIFIERS = [ - 'Development Status :: 4 - Beta', - 'Environment :: MacOS X', - 'Environment :: Win32 (MS Windows)', - 'Environment :: X11 Applications', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: MIT License', - 'Natural Language :: English', - 'Operating System :: Microsoft :: Windows', - 'Operating System :: POSIX', - 'Operating System :: MacOS :: MacOS X', - 'Programming Language :: Python :: 2.5', - 'Programming Language :: Python :: 2.6', - 'Topic :: Games/Entertainment', -] - -PLATFORMS = [ - 'Linux', - 'Mac OS X', - 'Windows', -] - -INSTALL_REQUIRES = [ -] - -# Install these manually -NON_EGG_REQUIREMENTS = [ - 'setuptools', - 'pygame', -] diff -r f95830b58336 -r 79b5c1be9a5e pyntnclick/widgets/__init__.py --- a/pyntnclick/widgets/__init__.py Sat Jun 21 22:04:35 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,4 +0,0 @@ -# __init__.py for the widgets -# Copyright Boomslang team, 2010-2012 (see COPYING File) - -"""pyntnclick widgets""" diff -r f95830b58336 -r 79b5c1be9a5e pyntnclick/widgets/base.py --- a/pyntnclick/widgets/base.py Sat Jun 21 22:04:35 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,275 +0,0 @@ -import collections - -import pygame -from pygame.locals import (MOUSEBUTTONDOWN, MOUSEBUTTONUP, - MOUSEMOTION, SRCALPHA, USEREVENT, - BLEND_RGBA_MIN) - -from pyntnclick.engine import UserEvent -from pyntnclick.utils import convert_color - - -class Widget(object): - - highlight_cursor = False - - def __init__(self, pos, gd, size): - self.pos = pos - self.gd = gd - self.resource = gd.resource - self.size = size - self.rect = pygame.Rect(pos, size if size else (0, 0)) - self.modal = False - self.parent = None - self.disabled = False - self.visible = True - self.callbacks = collections.defaultdict(list) - # To track which widget the mouse is over - self.mouseover_widget = self - self.is_prepared = False - - def set_parent(self, parent): - self.parent = parent - - 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 or not self.visible: - return False - - 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 prepare(self): - """Override me""" - pass - - def do_prepare(self): - if not self.is_prepared: - self.prepare() - self.is_prepared = True - - def disable(self): - if not self.disabled: - self.disabled = True - self.prepare() - self.is_prepared = True - - def enable(self): - if self.disabled: - self.disabled = False - self.prepare() - self.is_prepared = True - - def set_visible(self, visible): - if self.visible != visible: - self.visible = visible - self.prepare() - self.is_prepared = True - - def global_to_local(self, pos): - x, y = pos - return (x - self.rect.left, y - self.rect.top) - - -class Button(Widget): - - highlight_cursor = True - - def event(self, ev): - if super(Button, self).event(ev): - return True - if ev.type == MOUSEBUTTONDOWN: - 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""" - for callback, args in self.callbacks['clicked']: - if callback(None, self, *args): - return True - return False - - -class Container(Widget): - - def __init__(self, pos, gd, size=None): - super(Container, self).__init__(pos, gd, size) - self.children = [] - - def event(self, ev): - """Push an event down through the tree, and fire our own event as a - last resort - """ - self.mouseover_widget = self - if ev.type in (MOUSEMOTION, MOUSEBUTTONUP, MOUSEBUTTONDOWN): - for child in self.children[:]: - if child.rect.collidepoint(ev.pos): - result = child.event(ev) - self.mouseover_widget = child.mouseover_widget - if result: - 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 - - def add(self, widget): - widget.set_parent(self) - widget.prepare() - self.children.append(widget) - if not self.size: - self.rect = self.rect.union(widget.rect) - return widget - - def remove(self, widget): - widget.set_parent(None) - self.children.remove(widget) - - def remove_all(self): - for widget in reversed(self.children[:]): - self.remove(widget) - - def draw(self, surface): - if self.visible: - self.do_prepare() - for child in self.children: - child.draw(surface) - - -class ModalStackContainer(Container): - - def __init__(self, pos, gd, size, obscure_color=None): - super(ModalStackContainer, self).__init__(pos, gd, size) - if obscure_color is None: - obscure_color = gd.constants.modal_obscure_color - self.obscure_color = convert_color(obscure_color) - - @property - def top(self): - if self.children: - return self.children[-1] - return None - - def event(self, ev): - """Only the topmost child gets events. - """ - self.mouseover_widget = self - if self.top: - self.mouseover_widget = self.top.mouseover_widget - if self.top.event(ev): - return True - - # We skip Container's event() method and hop straight to its parent's. - if super(Container, self).event(ev): - return True - - def is_top(self, widget): - return self.top is widget - - def draw(self, surface): - if self.visible: - self.do_prepare() - obscure = pygame.Surface(self.rect.size, SRCALPHA) - obscure.fill(self.obscure_color) - for child in self.children: - surface.blit(obscure, self.rect) - child.draw(surface) - - -class Box(Container): - """A container that draws a filled background with a border""" - padding = 4 - - def draw(self, surface): - if self.visible: - self.do_prepare() - # TODO: Why isn't this done in prepare? - 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) - - -class ModalWrapper(Container): - "A wrapper around a widget that removes itself when a mouse click occurs" - - def __init__(self, widget, close_callback=None): - super(ModalWrapper, self).__init__(widget.rect.topleft, widget.gd, - widget.rect.size) - self.close_callback = close_callback - self.add(widget) - self.add_callback(MOUSEBUTTONDOWN, self.close) - widget.add_callback(MOUSEBUTTONDOWN, self.close) - - def close(self, ev, widget): - if self.parent: - self.parent.remove(self) - if self.close_callback: - self.close_callback() - return True - - -class Image(Widget): - """Basic widget that draws an image, with an associated rect""" - - def __init__(self, pos, gd, image, size=None): - super(Image, self).__init__(pos, gd, size) - self.image = image - if not size: - self.rect.size = image.get_rect().size - self.visible = True - - def draw(self, surface): - self.do_prepare() - if self.visible: - surface.blit(self.image, self.rect) - - -class TranslucentImage(Image): - """Image that can also be translucent""" - - def __init__(self, pos, gd, image, size=None): - super(TranslucentImage, self).__init__(pos, gd, image, size) - self.translucent = False - surf = pygame.surface.Surface((self.rect.width, self.rect.height), - SRCALPHA).convert_alpha() - surf.fill(pygame.color.Color(255, 255, 255, 96)) - surf.blit(self.image, (0, 0), None, BLEND_RGBA_MIN) - self.trans_image = surf - - def draw(self, surface): - self.do_prepare() - if self.visible: - if self.translucent: - surface.blit(self.trans_image, self.rect) - else: - surface.blit(self.image, self.rect) diff -r f95830b58336 -r 79b5c1be9a5e pyntnclick/widgets/filechooser.py --- a/pyntnclick/widgets/filechooser.py Sat Jun 21 22:04:35 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,164 +0,0 @@ -# Display a paged lsit of files and directories -# Allow moving up and down directory trees - -import os - -from pyntnclick.widgets.base import Box -from pyntnclick.widgets.text import TextButton, LabelWidget - - -class FileChooser(Box): - - def __init__(self, pos, gd, size, curdir, ok_callback, - page_length=12, padding=2): - super(FileChooser, self).__init__(pos, gd, size) - self.page_length = page_length - self.page = 0 - self.ok_callback = ok_callback - self.curdir = os.path.realpath(os.path.normpath(curdir)) - self.selected = None - self.padding = padding - self.dirs = [] - self.files = [] - self.modal = True - self.prev_but = None - self.next_but = None - - def get_lists(self): - self.dirs = [] - self.files = [] - for entry in sorted(os.listdir(self.curdir)): - path = os.path.join(self.curdir, entry) - if os.path.isdir(path): - self.dirs.append(entry) - else: - self.files.append(entry) - - def prepare(self): - super(FileChooser, self).prepare() - self.refresh() - - def refresh(self): - self.page = 0 - self.selected = None - self.get_lists() - self.fill_page() - - def _dir_button(self, entry): - widget = TextButton((0, 0), self.gd, entry + '/', - fontname=self.gd.constants.bold_font, - fontsize=10) - widget.do_prepare() - widget.add_callback('clicked', self.change_dir, entry) - return widget - - def _file_button(self, entry): - if entry == self.selected: - # highlight - widget = TextButton((0, 0), self.gd, entry, - fontsize=10, border=2, color='yellow') - else: - widget = TextButton((0, 0), self.gd, entry, border=0, - fontsize=10) - widget.do_prepare() - widget.add_callback('clicked', self.change_selection, entry) - return widget - - def fill_page(self): - for widget in self.children[:]: - self.remove(widget) - start_page = self.page * self.page_length - end_page = start_page + self.page_length - entries = self.dirs + self.files - top = self.rect.top + self.padding - left = self.rect.left + self.padding - # Add current directory at the top - widget = LabelWidget((left, top), self.gd, self.curdir[-30:], - color='black') - widget.do_prepare() - self.add(widget) - upbut = TextButton((left + 2 * self.padding + widget.rect.width, top), - self.gd, u'\N{LEFTWARDS ARROW WITH HOOK}Back one level') - upbut.do_prepare() - upbut.add_callback('clicked', self.change_dir, os.pardir) - self.add(upbut) - top += max(widget.rect.height, upbut.rect.height) + 4 * self.padding - page_top = top - page_left = left - top += self.padding - for entry in entries[start_page:end_page]: - if entry in self.dirs: - widget = self._dir_button(entry) - else: - widget = self._file_button(entry) - widget.rect.topleft = (left, top) - self.add(widget) - top += widget.rect.height + self.padding - page_left = max(page_left, left + widget.rect.width + self.padding) - # Add page list buttons - if not self.prev_but: - self.prev_but = TextButton((0, 0), self.gd, u'\N{UPWARDS ARROW}') - self.prev_but.do_prepare() - self.prev_but.add_callback('clicked', self.change_page, -1) - self.prev_but.rect.topleft = (page_left, page_top) - if not self.next_but: - self.next_but = TextButton((0, 0), self.gd, u'\N{DOWNWARDS ARROW}') - self.next_but.do_prepare() - self.next_but.add_callback('clicked', self.change_page, +1) - page_top = max(top - self.next_but.rect.height, - self.prev_but.rect.bottom + self.padding) - self.next_but.rect.topleft = (page_left, page_top) - if self.page > 0: - self.prev_but.enable() - else: - self.prev_but.disable() - if end_page + 1 < len(entries): - self.next_but.enable() - else: - self.next_but.disable() - self.add(self.next_but) - self.add(self.prev_but) - # Add OK and Cancel buttons - top = max(self.prev_but.rect.bottom + self.padding, - top + 2 * self.padding) - ok_but = TextButton((left, top), self.gd, 'OK') - ok_but.add_callback('clicked', self.ok) - self.add(ok_but) - cancel_but = TextButton( - (left + ok_but.rect.width + 4 * self.padding, top), - self.gd, 'Cancel') - cancel_but.add_callback('clicked', self.cancel) - self.add(cancel_but) - - def change_page(self, ev, widget, change): - self.page += change - self.fill_page() - return True - - def change_dir(self, ev, widget, newdir): - """Change directory and refresh the widget.""" - self.curdir = os.path.normpath(os.path.join(self.curdir, newdir)) - self.page = 0 - self.selected = None - self.get_lists() - self.fill_page() - - def change_selection(self, ev, widget, entry): - """Update selection""" - self.selected = entry - self.fill_page() - - def cancel(self, ev, widget): - if hasattr(self.parent, 'paused'): - self.parent.paused = False - self.parent.remove(self) - return True - - def ok(self, ev, widget): - if hasattr(self.parent, 'paused'): - self.parent.paused = False - self.parent.remove(self) - if self.selected: - self.ok_callback(os.path.normpath(os.path.join(self.curdir, - self.selected))) - return True diff -r f95830b58336 -r 79b5c1be9a5e pyntnclick/widgets/imagebutton.py --- a/pyntnclick/widgets/imagebutton.py Sat Jun 21 22:04:35 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,15 +0,0 @@ -from pyntnclick.widgets.base import Button - - -class ImageButtonWidget(Button): - """An image that is also a button. Whatever next?""" - - def __init__(self, pos, gd, image, size=None): - super(ImageButtonWidget, self).__init__(pos, gd, size) - if not size: - self.rect.size = image.get_rect().size - self.image = image - - def draw(self, surface): - if self.visible: - surface.blit(self.image, self.rect) diff -r f95830b58336 -r 79b5c1be9a5e pyntnclick/widgets/text.py --- a/pyntnclick/widgets/text.py Sat Jun 21 22:04:35 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,164 +0,0 @@ -from textwrap import wrap - -import pygame -from pygame.constants import SRCALPHA - -from pyntnclick.widgets.base import Widget, Button -from pyntnclick.utils import convert_color - - -class TextWidget(Widget): - def __init__(self, pos, gd, text, size=None, fontname=None, fontsize=None, - color=None): - super(TextWidget, self).__init__(pos, gd, size) - self.text = text - constants = self.gd.constants - self.fontname = fontname or constants.font - self.fontsize = fontsize or constants.font_size - self.color = color or constants.text_color - - def prepare(self): - self.font = self.resource.get_font(self.fontname, self.fontsize) - self.color = convert_color(self.color) - self.surface = self.font.render(self.text, True, self.color) - self.text_rect = self.surface.get_rect() - if not self.size: - self.rect.size = self.text_rect.size - - def draw(self, surface): - if self.visible: - self.do_prepare() - surface.blit(self.surface, self.rect) - - -class LabelWidget(TextWidget): - def __init__(self, pos, gd, *args, **kwargs): - constants = gd.constants - self.padding = kwargs.pop('padding', constants.label_padding) - self.border = kwargs.pop('border', constants.label_border) - self.bg_color = convert_color( - kwargs.pop('bg_color', constants.label_bg_color)) - self.border_color = convert_color( - kwargs.pop('border_color', constants.label_border_color)) - super(LabelWidget, self).__init__(pos, gd, *args, **kwargs) - - def prepare(self): - super(LabelWidget, self).prepare() - if not self.size: - self.rect.width += 2 * self.padding - self.rect.height += 2 * self.padding - new_surface = pygame.Surface(self.rect.size) - new_surface = new_surface.convert_alpha() - new_surface.fill(self.bg_color) - new_surface.blit(self.surface, self.surface.get_rect().move( - (self.padding, self.padding))) - if self.border: - pygame.draw.rect(new_surface, self.border_color, - new_surface.get_rect(), - self.border) - self.surface = new_surface - - def draw(self, surface): - if self.visible: - self.do_prepare() - surface.blit(self.surface, self.rect) - - -class TextButton(Button, TextWidget): - def __init__(self, pos, gd, *args, **kwargs): - constants = gd.constants - self.padding = kwargs.pop('padding', constants.label_padding) - self.border = kwargs.pop('border', constants.label_border) - - kwargs['color'] = convert_color( - kwargs.pop('color', constants.button_color)) - self.disabled_color = convert_color( - kwargs.pop('disabled_color', constants.button_disabled_color)) - self.bg_color = convert_color( - kwargs.pop('bg_color', constants.button_bg_color)) - - super(TextButton, self).__init__(pos, gd, *args, **kwargs) - - def prepare(self): - super(TextButton, self).prepare() - text = self.surface - text_rect = self.text_rect - color = self.disabled_color if self.disabled else self.color - - width = text_rect.width + self.padding * 2 - height = text_rect.height + self.padding * 2 - if not self.size: - 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(self.bg_color) - offset = ( - (self.rect.width - width) / 2 + self.padding, - (self.rect.height - height) / 2 + self.padding) - self.surface.blit(text, text.get_rect().move(offset)) - - if self.border: - pygame.draw.rect(self.surface, color, self.surface.get_rect(), - self.border) - - def draw(self, surface): - super(TextButton, self).draw(surface) - - -class WrappedTextLabel(LabelWidget): - """A Label Widget that wraps the text to a given maximum width""" - - def __init__(self, pos, gd, *args, **kwargs): - self.max_width = kwargs.pop('max_width', gd.constants.screen[0] - 50) - self._wrap_width = None - self._text_lines = None - super(WrappedTextLabel, self).__init__(pos, gd, *args, **kwargs) - - def prepare(self): - if self._wrap_width is None: - # Start without wrapping - self._wrap_width = len(self.text) + 1 - self._text_lines = [self.text] - - self.font = self.resource.get_font(self.fontname, self.fontsize) - self.color = convert_color(self.color) - self._render() - self.text_rect = self.surface.get_rect() - width, height = self.surface.get_rect().size - while width > self.max_width: - # Very simplistic approach - self._wrap_width = self._wrap_width / 2 - self._text_lines = wrap(self.text, self._wrap_width) - self._render() - width, height = self.surface.get_rect().size - if not self.size: - self.rect.width = max(self.rect.width, width) - self.rect.height = max(self.rect.height, height) - - if self.border: - pygame.draw.rect(self.surface, self.border_color, - self.surface.get_rect(), - self.border) - - def _render(self): - surfaces = [] - width = 0 - height = 0 - for line in self._text_lines: - line_surf = self.font.render(line, True, self.color) - surfaces.append(line_surf) - width = max(line_surf.get_rect().width, width) - height += line_surf.get_rect().height - - width += 2 * self.padding - height += 2 * self.padding - - self.surface = pygame.Surface((width, height)) - self.surface = self.surface.convert_alpha() - self.surface.fill(self.bg_color) - height = self.padding - for line_surf in surfaces: - rect = pygame.Rect((self.padding, height), - (line_surf.get_rect().size)) - self.surface.blit(line_surf, rect) - height += line_surf.get_rect().height diff -r f95830b58336 -r 79b5c1be9a5e pyntnclick/widgets/toollist.py --- a/pyntnclick/widgets/toollist.py Sat Jun 21 22:04:35 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,64 +0,0 @@ -from pyntntclick.widgets.base import Container -from pyntntclick.widgets.text import TextButton - - -class ToolListWidget(Container): - """List of other widgets, with some paging trickery""" - - def __init__(self, pos, gd, size, widget_list, page_length, - 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 - super(ToolListWidget, self).__init__(pos, gd, size) - self.prev_but = None - self.next_but = None - - def prepare(self): - self.fill_page() - - def fill_page(self): - for widget in self.children[:]: - self.remove(widget) - 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 - for widget in self.widget_list[start_page:end_page]: - widget.rect.topleft = (button_left, button_height) - self.add(widget) - 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 change_page(self, ev, widget, change): - self.page += change - self.fill_page() - return True