Text rendering restructured
This commit is contained in:
200
gui.py
200
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:
|
||||
|
||||
Reference in New Issue
Block a user