233 lines
8.1 KiB
Python
233 lines
8.1 KiB
Python
#!/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
|
|
|
|
try:
|
|
import pygame.fastevent as EventModule
|
|
except ImportError:
|
|
import pygame.event as EventModule
|
|
|
|
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 GUI_PyGame:
|
|
"""A GUI class using PyGame"""
|
|
|
|
def __init__(self, name, size):
|
|
# Call init routines
|
|
pygame.init()
|
|
if hasattr(EventModule, 'init'):
|
|
EventModule.init()
|
|
|
|
# Window name
|
|
pygame.display.set_caption(name)
|
|
|
|
# Hide mouse cursor
|
|
pygame.mouse.set_cursor(*pygame.cursors.load_xbm('transparent.xbm','transparent.msk'))
|
|
|
|
# Store screen and size
|
|
self.size = size
|
|
self.screen = pygame.display.set_mode(size, pygame.FULLSCREEN)
|
|
|
|
# Clear screen
|
|
self.clear()
|
|
|
|
def clear(self, color=(0,0,0)):
|
|
self.screen.fill(color)
|
|
self.surface_list = []
|
|
|
|
def apply(self):
|
|
for surface in self.surface_list:
|
|
self.screen.blit(surface[0], surface[1])
|
|
pygame.display.update()
|
|
|
|
def get_size(self):
|
|
return self.size
|
|
|
|
def trigger_event(self, event_channel):
|
|
EventModule.post(EventModule.Event(pygame.USEREVENT, channel=event_channel))
|
|
|
|
def show_picture(self, filename, size=(0,0), offset=(0,0)):
|
|
# Use window size if none given
|
|
if size == (0,0):
|
|
size = self.size
|
|
# Load image from file
|
|
image = pygame.image.load(filename)
|
|
# 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)])
|
|
# New image size
|
|
new_size = [int(a*image_scale) for a in image_size]
|
|
# Update offset
|
|
offset = tuple(a+int((b-c)/2) for a,b,c in zip(offset, size, new_size))
|
|
# Apply scaling and display picture
|
|
image = pygame.transform.scale(image, new_size).convert()
|
|
# Create surface and blit the image to it
|
|
surface = pygame.Surface(new_size)
|
|
surface.blit(image, (0,0))
|
|
self.surface_list.append((surface, offset))
|
|
|
|
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)
|
|
if transparency:
|
|
text.set_colorkey(bg)
|
|
self.surface_list.append((text, rect.topleft))
|
|
|
|
def wait_for_event(self):
|
|
# Repeat until a relevant event happened
|
|
while True:
|
|
# Discard all input that happened before entering the loop
|
|
EventModule.get()
|
|
|
|
# Wait for event
|
|
event = EventModule.wait()
|
|
|
|
# Return Event-Object
|
|
if event.type == pygame.QUIT:
|
|
return Event(0, 0)
|
|
elif event.type == pygame.KEYDOWN:
|
|
return Event(1, event.key)
|
|
elif event.type == pygame.MOUSEBUTTONUP:
|
|
return Event(2, (event.button, event.pos))
|
|
elif event.type >= pygame.USEREVENT:
|
|
return Event(3, event.channel)
|
|
|
|
|
|
def mainloop(self, filename, handle_keypress, handle_mousebutton, handle_gpio_event):
|
|
while True:
|
|
# Ignore all input that happened before entering the loop
|
|
EventModule.get()
|
|
# Clear display
|
|
self.clear()
|
|
# Show idle-picture and message
|
|
if filename != None:
|
|
self.show_picture(filename)
|
|
self.show_message("Hit the button!")
|
|
# Render everything
|
|
self.apply()
|
|
# Wait for event
|
|
event = EventModule.wait()
|
|
# Handle the event
|
|
if event.type == pygame.QUIT: return
|
|
elif event.type == pygame.KEYDOWN: handle_keypress(event.key)
|
|
elif event.type == pygame.MOUSEBUTTONUP: handle_mousebutton(event.button, event.pos)
|
|
elif event.type == gpio_pygame_event: handle_gpio_event(event.channel)
|
|
|
|
def teardown(self):
|
|
pygame.quit()
|