diff --git a/gui.py b/gui.py index bccfdb9..f8cc6f4 100644 --- a/gui.py +++ b/gui.py @@ -1,11 +1,6 @@ #!/usr/bin/env python # Created by br@re-web.eu, 2015 -# TODO: -# - base everything on surfaces to allow stacking -# - incorporate render_textrect -# - restructure mainloop - from __future__ import division import pygame @@ -17,108 +12,8 @@ except ImportError: from events import Event - -class TextRectException: - def __init__(self, message = None): - self.message = message - def __str__(self): - return self.message - -def render_textrect(string, font, rect, text_color, background_color, justification=0, valign=0): - """Returns a surface containing the passed text string, reformatted - to fit within the given rect, word-wrapping as necessary. The text - will be anti-aliased. - - Source: http://www.pygame.org/pcr/text_rect/index.php - - Takes the following arguments: - - string - the text you wish to render. \n begins a new line. - font - a Font object - rect - a rectstyle giving the size of the surface requested. - text_color - a three-byte tuple of the rgb value of the - text color. ex (0, 0, 0) = BLACK - background_color - a three-byte tuple of the rgb value of the surface. - justification - 0 (default) left-justified - 1 horizontally centered - 2 right-justified - valign - 0 (default) top aligned - 1 vertically centered - 2 bottom aligned - - Returns the following values: - - Success - a surface object with the text rendered onto it. - Failure - raises a TextRectException if the text won't fit onto the surface. - """ - - final_lines = [] - - requested_lines = string.splitlines() - - # Create a series of lines that will fit on the provided - # rectangle. - - accumulated_height = 0 - for requested_line in requested_lines: - if font.size(requested_line)[0] > rect.width: - words = requested_line.split(' ') - # if any of our words are too long to fit, return. - for word in words: - if font.size(word)[0] >= rect.width: - raise TextRectException, "The word " + word + " is too long to fit in the rect passed." - # Start a new line - accumulated_line = "" - for word in words: - test_line = accumulated_line + word + " " - # Build the line while the words fit. - if font.size(test_line)[0] < rect.width: - accumulated_line = test_line - else: - accumulated_height += font.size(test_line)[1] - final_lines.append(accumulated_line) - accumulated_line = word + " " - accumulated_height += font.size(accumulated_line)[1] - final_lines.append(accumulated_line) - else: - accumulated_height += font.size(requested_line)[1] - final_lines.append(requested_line) - - # Check height of the text and align vertically - - if accumulated_height >= rect.height: - raise TextRectException, "Once word-wrapped, the text string was too tall to fit in the rect." - - if valign == 0: - voffset = 0 - elif valign == 1: - voffset = int((rect.height - accumulated_height) / 2) - elif valign == 2: - voffset = rect.height - accumulated_height - else: - raise TextRectException, "Invalid valign argument: " + str(valign) - - # Let's try to write the text out on the surface. - - surface = pygame.Surface(rect.size) - surface.fill(background_color) - - accumulated_height = 0 - for line in final_lines: - if line != "": - tempsurface = font.render(line, 1, text_color) - if justification == 0: - surface.blit(tempsurface, (0, voffset + accumulated_height)) - elif justification == 1: - surface.blit(tempsurface, ((rect.width - tempsurface.get_width()) / 2, voffset + accumulated_height)) - elif justification == 2: - surface.blit(tempsurface, (rect.width - tempsurface.get_width(), voffset + accumulated_height)) - else: - raise TextRectException, "Invalid justification argument: " + str(justification) - accumulated_height += font.size(line)[1] - - return surface - +class GuiException(Exception): + """Custom exception class to handle GUI class errors""" class GUI_PyGame: """A GUI class using PyGame""" @@ -166,7 +61,7 @@ class GUI_PyGame: # Load image from file image = pygame.image.load(filename) except pygame.error as e: - print("ERROR: Can't open image '" + filename + "': " + e.message) + raise GuiException("ERROR: Can't open image '" + filename + "': " + e.message) # Extract image size and determine scaling image_size = image.get_rect().size image_scale = min([min(a,b)/b for a,b in zip(size, image_size)]) @@ -184,13 +79,90 @@ class GUI_PyGame: def show_message(self, msg, color=(245,245,245), bg=(0,0,0), transparency=True): # Choose font font = pygame.font.Font(None, 144) - # Create rectangle for text - rect = pygame.Rect((0, 0, self.size[0], self.size[1])) - # Render text - text = render_textrect(msg, font, rect, color, bg, 1, 1) + # Wrap and render text + wrapped_text, text_height = self.wrap_text(msg, font, self.size) + rendered_text = self.render_text(wrapped_text, text_height, 1, 1, font, color, bg, transparency) + + self.surface_list.append((rendered_text, (0,0))) + + def wrap_text(self, msg, font, size): + final_lines = [] # resulting wrapped text + requested_lines = msg.splitlines() # wrap input along line breaks + accumulated_height = 0 # accumulated height + + # Form a series of lines + for requested_line in requested_lines: + # Handle too long lines + if font.size(requested_line)[0] > size[0]: + # Split at white spaces + words = requested_line.split(' ') + # if any of our words are too long to fit, return. + for word in words: + if font.size(word)[0] >= size[0]: + raise GuiException("The word " + word + " is too long to fit.") + # Start a new line + accumulated_line = "" + # Put words on the line as long as they fit + for word in words: + test_line = accumulated_line + word + " " + # Build the line while the words fit. + if font.size(test_line)[0] < size[0]: + accumulated_line = test_line + else: + accumulated_height += font.size(test_line)[1] + final_lines.append(accumulated_line) + accumulated_line = word + " " + # Finish requested_line + accumulated_height += font.size(accumulated_line)[1] + final_lines.append(accumulated_line) + # Line fits as it is + else: + accumulated_height += font.size(requested_line)[1] + final_lines.append(requested_line) + + # Check height of wrapped text + if accumulated_height >= size[1]: + raise GuiException("Wrapped text is too high to fit.") + + return final_lines, accumulated_height + + def render_text(self, text, text_height, valign, halign, font, color, bg, transparency): + # Determine vertical position + if valign == 0: # top aligned + voffset = 0 + elif valign == 1: # centered + voffset = int((self.size[1] - text_height) / 2) + elif valign == 2: # bottom aligned + voffset = self.size[1] - text_height + else: + raise GuiException("Invalid valign argument: " + str(valign)) + + # Create Surface object and fill it with the given background + surface = pygame.Surface(self.size) + surface.fill(bg) + + # Blit one line after another + accumulated_height = 0 + for line in text: + if line != "": + tempsurface = font.render(line, 1, color) + if halign == 0: # left aligned + hoffset = 0 + elif halign == 1: # centered + hoffset = (self.size[0] - tempsurface.get_width()) / 2 + elif halign == 2: # right aligned + hoffset = rect.width - tempsurface.get_width() + else: + raise GuiException("Invalid halign argument: " + str(justification)) + surface.blit(tempsurface, (hoffset, voffset + accumulated_height)) + accumulated_height += font.size(line)[1] + + # Make background color transparent if transparency: - text.set_colorkey(bg) - self.surface_list.append((text, rect.topleft)) + surface.set_colorkey(bg) + + # Return the rendered surface + return surface def convert_event(self, event): if event.type == pygame.QUIT: