Text rendering restructured
This commit is contained in:
200
gui.py
200
gui.py
@@ -1,11 +1,6 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# Created by br@re-web.eu, 2015
|
# Created by br@re-web.eu, 2015
|
||||||
|
|
||||||
# TODO:
|
|
||||||
# - base everything on surfaces to allow stacking
|
|
||||||
# - incorporate render_textrect
|
|
||||||
# - restructure mainloop
|
|
||||||
|
|
||||||
from __future__ import division
|
from __future__ import division
|
||||||
|
|
||||||
import pygame
|
import pygame
|
||||||
@@ -17,108 +12,8 @@ except ImportError:
|
|||||||
|
|
||||||
from events import Event
|
from events import Event
|
||||||
|
|
||||||
|
class GuiException(Exception):
|
||||||
class TextRectException:
|
"""Custom exception class to handle GUI class errors"""
|
||||||
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 GUI_PyGame:
|
class GUI_PyGame:
|
||||||
"""A GUI class using PyGame"""
|
"""A GUI class using PyGame"""
|
||||||
@@ -166,7 +61,7 @@ class GUI_PyGame:
|
|||||||
# Load image from file
|
# Load image from file
|
||||||
image = pygame.image.load(filename)
|
image = pygame.image.load(filename)
|
||||||
except pygame.error as e:
|
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
|
# Extract image size and determine scaling
|
||||||
image_size = image.get_rect().size
|
image_size = image.get_rect().size
|
||||||
image_scale = min([min(a,b)/b for a,b in zip(size, image_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):
|
def show_message(self, msg, color=(245,245,245), bg=(0,0,0), transparency=True):
|
||||||
# Choose font
|
# Choose font
|
||||||
font = pygame.font.Font(None, 144)
|
font = pygame.font.Font(None, 144)
|
||||||
# Create rectangle for text
|
# Wrap and render text
|
||||||
rect = pygame.Rect((0, 0, self.size[0], self.size[1]))
|
wrapped_text, text_height = self.wrap_text(msg, font, self.size)
|
||||||
# Render text
|
rendered_text = self.render_text(wrapped_text, text_height, 1, 1, font, color, bg, transparency)
|
||||||
text = render_textrect(msg, font, rect, color, bg, 1, 1)
|
|
||||||
|
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:
|
if transparency:
|
||||||
text.set_colorkey(bg)
|
surface.set_colorkey(bg)
|
||||||
self.surface_list.append((text, rect.topleft))
|
|
||||||
|
# Return the rendered surface
|
||||||
|
return surface
|
||||||
|
|
||||||
def convert_event(self, event):
|
def convert_event(self, event):
|
||||||
if event.type == pygame.QUIT:
|
if event.type == pygame.QUIT:
|
||||||
|
|||||||
Reference in New Issue
Block a user