view gamelib/gui_base.py @ 212:16ce5ed563c9

Change science buttons
author Neil Muller <drnlmuller@gmail.com>
date Sat, 12 May 2012 20:17:28 +0200
parents 53277724645b
children ce1e23ea46e5
line wrap: on
line source

import pygame
from pygame.locals import SRCALPHA
from pygame import Surface, Rect
from pygame.font import Font

from gamelib import data


# different font sizes
pygame.font.init()
font_small = Font(data.filepath('fonts/DejaVuSans.ttf'), 10)
font_medium = Font(data.filepath('fonts/DejaVuSans.ttf'), 14)
font_large = Font(data.filepath('fonts/DejaVuSans.ttf'), 18)
font_auto = None


class Drawable(object):

    def __init__(self, rect):
        if isinstance(rect, Rect):
            self.rect = rect
        else:
            self.rect = Rect(rect[0], rect[1], rect[2], rect[3])

    def draw(self, surface):
        pass


class Clickable(object):

    def on_mouse_down(self, pos):
        pass

    def on_mouse_up(self, pos):
        pass

    def on_mouse_move(self, pos):
        pass

    def on_mouse_cancel(self):
        pass

    def on_click(self):
        pass


class ContainerView(Drawable):

    def __init__(self):
        self.children = []

    def add_child(self, child):
        self.children.append(child)

    def remove_child(self, child):
        self.children.remove(child)

    def get_child_by_pos(self, pos):
        for child in self.children:
            if child.rect.collidepoint(pos):
                if isinstance(child, ContainerView):
                    # calculates position relative to child
                    x = pos[0] - child.rect[0]
                    y = pos[1] - child.rect[1]
                    return child.get_child_by_pos((x, y))
                else:
                    return child

    def draw_children(self, surface):
        for child in self.children:
            child.draw(surface)


class Window(Clickable, ContainerView):

    def __init__(self, screen):
        super(Window, self).__init__()
        self.surface = Surface((screen.get_width(), screen.get_height()))
        self.background_colour = None
        self.background_image = None
        self.pressed_child = None

    def on_mouse_down(self, pos):
        child = self.get_child_by_pos(pos)
        if isinstance(child, Clickable):
            # calculates position relative to child
            x = pos[0] - child.rect[0]
            y = pos[1] - child.rect[1]
            child.on_mouse_down((x, y))
            self.pressed_child = child

    def on_mouse_up(self, pos):
        if self.pressed_child:
            child = self.pressed_child
            x = pos[0] - child.rect[0]
            y = pos[1] - child.rect[1]
            child.on_mouse_up((x, y))
            self.pressed_child = None

    def on_mouse_move(self, pos):
        if self.pressed_child:
            if self.pressed_child != self.get_child_by_pos(pos):
                self.pressed_child.on_mouse_cancel()
                self.pressed_child = None

    def draw(self, screen):
        if self.background_colour:
            self.surface.fill(self.background_colour)
        else:
            self.surface.fill((0, 0, 0, 255))
        if self.background_image:
            self.surface.blit(self.background_image, (0, 0))
        self.draw_children(self.surface)
        screen.blit(self.surface, (0, 0))


class StateDrawable(Drawable):

    def __init__(self, rect):
        super(StateDrawable, self).__init__(rect)
        self.state = -1
        self.states = {}
        self.drawables = []

    def draw(self, surface):
        if self.state != -1 and self.drawables[self.state]:
            self.drawables[self.state].draw(surface)

    def add_state(self, state_name, drawable):
        self.states[state_name] = len(self.drawables)
        self.drawables.append(drawable)

    def set_state(self, state_name):
        self.state = self.states[state_name]


class Button(Clickable, StateDrawable):

    def __init__(self, rect, normal_drawable, down_drawable):
        super(Button, self).__init__(rect)
        self.add_state('NORMAL', normal_drawable)
        self.add_state('DOWN', down_drawable)
        self.set_state('NORMAL')

    def on_mouse_down(self, pos):
        self.set_state('DOWN')

    def on_mouse_up(self, pos):
        self.set_state('NORMAL')
        self.on_click()

    def on_mouse_cancel(self):
        self.set_state('NORMAL')


class TextButton(Button):

    def __init__(self, rect, normal_drawable, down_drawable, text, font,
                 shadow, text_rect=None, color=(0, 0, 0),
                 shadow_color=(128, 128, 128)):
        super(TextButton, self).__init__(rect, normal_drawable, down_drawable)
        self.text_rect = Rect((0, 0), self.rect.size)
        if text_rect is not None:
            self.text_rect = Rect(*text_rect)
        self.text = text
        if font is font_auto:
            font = self._auto_font()
        self.font = font
        self.shadow = shadow
        self.shadow_color = shadow_color
        self.color = color
        self._draw_text()

    def _auto_font(self):
        for font in (font_large, font_medium):
            h, w = font.size(self.text)
            if w < self.text_rect.width and h < self.text_rect.height:
                return font
        return font_small

    def _draw_text(self):
        self.font.set_bold(True)
        self.text_surface = self.font.render(self.text, True, self.color)
        size = self.font.size(self.text)
        if self.shadow:
            s = self.font.render(self.text, True, self.shadow_color)
            temp = Surface((s.get_width() + 1, s.get_width() + 1), SRCALPHA)
            temp.fill((255, 255, 255, 0))
            temp.blit(s, (1, 1))
            temp.blit(self.text_surface, (0, 0))
            self.text_surface = temp
            size = [s + 2 for s in size]
        self.text_offset = Rect((0, 0), size)
        self.text_offset.center = self.text_rect.center
        self.font.set_bold(False)

    def draw(self, surface):
        temp_surface = Surface(self.rect.size, SRCALPHA)
        super(TextButton, self).draw(temp_surface)
        temp_surface.blit(self.text_surface, self.text_offset)
        surface.blit(temp_surface, self.rect)


class ToggleButton(Button):

    def __init__(self, rect, normal_drwble, down_drwble):
        super(ToggleButton, self).__init__(rect, normal_drwble, down_drwble)
        self.toggled = False

    def on_click(self):
        self.toggled = not self.toggled
        if self.toggled:
            self.set_state('DOWN')
        else:
            self.set_state('NORMAL')


class TextLabel(Drawable):

    def __init__(self, rect, text, font, color):
        super(TextLabel, self).__init__(rect)
        self.surface = Surface((rect[2], rect[3]), pygame.SRCALPHA)
        self.text = text
        self.font = font
        self.color = color
        self._draw_text()

    def _draw_text(self):
        self.text_surface = self.font.render(self.text, True, self.color)
        size = self.font.size(self.text)
        # We centre vertically, but start at left edge
        self.text_offset = (0, (self.rect[3] - size[1]) / 2)

    def draw(self, surface):
        self.surface.fill((0, 0, 0, 0))
        super(TextLabel, self).draw(self.surface)
        self.surface.blit(self.text_surface, self.text_offset)
        surface.blit(self.surface, self.rect)


class TextBox(TextLabel):

    def _draw_text(self):
        self.text_surface = Surface((self.rect[2], self.rect[3]),
            pygame.SRCALPHA)
        current_height = 0
        current_str = ''
        size = None
        for word in self.text.split(' '):
            size = self.font.size(current_str + word)
            if size[0] < self.rect.width:
                current_str += word + ' '
            else:
                s = self.font.render(current_str[0:-1], True, self.color)
                self._resize_text_surface(current_height + size[1])
                self.text_surface.blit(s, (0, current_height))
                current_height += size[1]
                current_str = word + ' '
        if current_str[-1] == ' ':
            current_str = current_str[0:-1]
        s = self.font.render(current_str, True, self.color)
        self._resize_text_surface(current_height + size[1])
        self.text_surface.blit(s, (0, current_height))
        self.text_offset = (0, 0)
        self.rect.height = current_height + size[1]
        self.surface = Surface((self.rect.width, self.rect.height),
            pygame.SRCALPHA)

    def _resize_text_surface(self, new_height):
        """Resize the text surface if needed"""
        if new_height > self.text_surface.get_rect().height:
            old_text = self.text_surface
            self.text_surface = Surface((self.rect[2], new_height),
                    pygame.SRCALPHA)
            self.text_surface.blit(old_text, (0, 0))