Files
photobooth/gui.py
2015-06-28 19:04:24 +02:00

201 lines
7.1 KiB
Python

#!/usr/bin/env python
# Created by br@re-web.eu, 2015
from __future__ import division
import pygame
try:
import pygame.fastevent as EventModule
except ImportError:
import pygame.event as EventModule
from events import Event
class GuiException(Exception):
"""Custom exception class to handle GUI class errors"""
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()
self.apply()
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
try:
# Load image from file
image = pygame.image.load(filename)
except pygame.error as e:
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)])
# 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)
# 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:
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:
surface.set_colorkey(bg)
# Return the rendered surface
return surface
def convert_event(self, event):
if event.type == pygame.QUIT:
return True, Event(0, 0)
elif event.type == pygame.KEYDOWN:
return True, Event(1, event.key)
elif event.type == pygame.MOUSEBUTTONUP:
return True, Event(2, (event.button, event.pos))
elif event.type >= pygame.USEREVENT:
return True, Event(3, event.channel)
else:
return False, ''
def check_for_event(self):
for event in EventModule.get():
r, e = self.convert_event(event)
if r:
return r, e
return False, ''
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
r, e = self.convert_event(event)
if r:
return e
def teardown(self):
pygame.quit()