gummo.py
Do with it as ya like. But ya probably would like it best in the ptext
demo directory. :)
Gumm
On 3/16/2015 11:01 PM, bw wrote:
Yes...I have acquired clunx capacitor 518400. No one can stop me now.
And with this new font module I will win the Pyweek!
Oh, uh. Sorry, got a little excited. :)
I really like the convenience of this module, Christopher. I look
forward to playing with it some. Some of those effects and alignments
are a bit of a pain to code from scratch. The interface is as easy to
use as an auto-clunker!
Very nice.
Those are some handsome fonts.
Gumm
On 3/16/2015 9:13 PM, Christopher Night wrote:
Please let me know if you have any feedback or criticism on the
module I have written here:
https://github.com/cosmologicon/pygame-text
This module provides what is, for me, a more convenient interface to
drawing text with pygame.font. It also provides a few effects, such
as outlines and drop shadows. I plan to use this module in future
PyWeeks.
Thanks,
Christopher
#!/usr/bin/env python
"""gummo.py - An interactive demo by Gummbum for the ptext module
Usage:
python gummo.py -h
"""
# Note: My SysFont choices are wimpy in Windows 7, and ptext does not support
SysFonts. If SysFonts ever become a choice
# the behavior can be enablded at Settings.enable_sysfonts.
import argparse
import re
import pygame
from pygame.locals import *
import ptext
class Settings(object):
screen_width, screen_height = 1204, 768
gui_font = 'Roboto_Condensed'
gui_font_size = 16
use_sysfonts = False
font_dir = 'fonts'
bg_image = None
menu_labels = "Name Size FGColor GColor BGColor Outline Shadow Alpha
Antialias TextAlign BGImage".split()
font_names = ['Boogaloo', 'Bubblegum_Sans', 'CherryCreamSoda',
'Roboto_Condensed']
text = """
To clunk, or not to clunk--that is the clunk:
Whether 'tis clookier in the clunk to clunk
The clunx and clunks of clooky clunk
Or to clunk clunks against a clunk of clunx
And by clunking clunk them.
"""
enable_sysfonts = False # enable sysfont behavior (needs sysfont support
in ptext module)
def size(font, things):
max_w = max_h = 0
for text in [str(t) for t in things]:
w, h = font.size(text)
max_w = w if w > max_w else max_w
max_h = h if h > max_h else max_h
return max_w, max_h
def size_ints(font, start, end, step):
return size(font, [str(i) for i in range(start, end, step) + [end]])
def size_floats(font, start, end, step, precision):
def frange(start, end=None, step=1.0, precision=2):
assert step >= 10 ** -precision
if end is None:
end = start + 0.0
start = 0.0
if step is None:
step = 1.0
precision = int(abs(precision))
n = round(start, precision)
while True:
yield n
n += step
if step > 0 and n >= end:
break
elif step < 0 and n <= end:
break
return size(font, frange(start, end, step, precision))
def make_text_labels(font, texts):
max_w, max_h = size(font, texts)
return [Label(Rect(15, 15 + max_h * i, max_w, max_h), t) for i, t in
enumerate(texts)]
class Label(object):
def __init__(self, rect, text):
self.rect = rect
self.text = text
def get_text(self):
return self.text
def draw(self, **ptext_args):
ptext.drawbox(self.text, self.rect, anchor=(1, 0), **ptext_args)
def make_text_menu(font, texts, topleft):
dim = size(font, texts)
return TextThing(Rect(topleft, dim), texts)
class TextThing(object):
def __init__(self, rect, text_list):
self.rect = rect
self.text_list = text_list
self.text = text_list[0]
def scroll(self, way, pos):
if way == 'down':
self.get_prev(pos)
elif way == 'up':
self.get_next(pos)
def get_text(self):
return self.text
def get_next(self, pos=None):
t = self.text_list.pop(0)
self.text_list.append(t)
self.text = self.text_list[0]
return self.text
def get_prev(self, pos=None):
t = self.text_list.pop()
self.text_list.insert(0, t)
self.text = t
return t
def draw(self, **ptext_args):
ptext.draw(self.text, self.rect.topleft, **ptext_args)
def __str__(self):
return self.get_text()
class IntThing(object):
def __init__(self, rect, value, min_value, max_value, step=1):
self.rect = rect
self.value = value
self.min_value = min_value
self.max_value = max_value
self.step = step
self.text = str(value)
def scroll(self, way, pos):
if way == 'down':
self.get_prev(pos)
elif way == 'up':
self.get_next(pos)
def get_text(self):
return self.text
def get_next(self, pos=None):
return self._increment(self.step)
def get_prev(self, pos=None):
return self._increment(-self.step)
def _increment(self, step):
n = self.value + step
if n < self.min_value:
n = self.min_value
elif n > self.max_value:
n = self.max_value
self.value = n
self.text = str(n)
return self.text
def draw(self, **ptext_args):
ptext.draw(self.text, self.rect.topleft, **ptext_args)
def __str__(self):
return self.get_text()
class FloatThing(object):
def __init__(self, rect, value, min_value, max_value, step=0.1,
precision=1):
self.rect = rect
self.value = round(value, precision)
self.min_value = min_value
self.max_value = max_value
self.step = step
self.precision = precision
self.fmt = '{:0.' + str(precision) + 'f}'
self.text = self.fmt.format(self.value)
def scroll(self, way, pos):
if way == 'down':
self.get_prev(pos)
elif way == 'up':
self.get_next(pos)
def get_text(self):
return self.text
def get_next(self, pos=None):
return self._increment(self.step)
def get_prev(self, pos=None):
return self._increment(-self.step)
def _increment(self, step):
n = self.value + step
if n < self.min_value:
n = self.min_value
elif n > self.max_value:
n = self.max_value
self.value = round(n, self.precision)
self.text = self.fmt.format(self.value)
return self.text
def draw(self, **ptext_args):
ptext.draw(self.text, self.rect.topleft, **ptext_args)
def __str__(self):
return self.get_text()
def make_boolean_menu(font, value, topleft):
dim = size(font, ['True', 'False'])
return BooleanThing(Rect(topleft, dim), value)
class BooleanThing(object):
def __init__(self, rect, value):
self.rect = rect
self.value = value
self.text = str(value)
def scroll(self, way, pos):
if way == 'down':
self.get_prev(pos)
elif way == 'up':
self.get_next(pos)
def get_text(self):
return self.text
def get_next(self, pos=None):
return self._increment()
def get_prev(self, pos=None):
return self._increment()
def _increment(self):
self.value = not self.value
self.text = str(self.value)
return self.text
def draw(self, **ptext_args):
ptext.draw(self.text, self.rect.topleft, **ptext_args)
def __str__(self):
return self.get_text()
def make_color_menu(font, value, topleft):
x, y = size(font, [s * 3 for s in '0123456789'])
x *= 3 # three for r, g, b
x += 3 + 3 # three pixels between columns
return ColorThing(Rect(topleft, (x, y)), value)
class ColorThing(object):
def __init__(self, rect, color):
self.rect = rect
if isinstance(color, Color):
self.color = color
elif isinstance(color, str):
self.color = Color(color)
else:
self.color = Color(*color)
self.text = 'c.r c.g c.b'.format(c=self.color)
x, y, w, h = self.rect
self.rects = dict(zip('rgb', [Rect(x + 2 * n + w * n / 3.0, y, w / 3,
h) for n in (0, 1, 2)]))
def scroll(self, way, pos):
if way == 'down':
self.get_prev(pos)
elif way == 'up':
self.get_next(pos)
def get_text(self):
return self.text
def get_next(self, mouse_pos):
return self._increment(mouse_pos, 5)
def get_prev(self, mouse_pos):
return self._increment(mouse_pos, -5)
def draw(self, **ptext_args):
for c in 'rgb':
rect = self.rects[c]
ptext.draw('{:03d}'.format(getattr(self.color, c)), rect.topleft,
**ptext_args)
def _increment(self, mouse_pos, step):
color = dict(zip('rgb', self.color[:3]))
for c in 'rgb':
if self.rects[c].collidepoint(mouse_pos):
n = color[c] + step
if n > 255:
n = 255
elif n < 0:
n = 0
setattr(self.color, c, n)
self.text = 'c.r c.g c.b'.format(c=self.color)
return self.get_text()
def __str__(self):
return self.get_text()
class Tweaker(object):
def __init__(self):
ptext.FONT_NAME_TEMPLATE = '{}/%s.ttf'.format(Settings.font_dir)
self.screen = pygame.display.set_mode((Settings.screen_width,
Settings.screen_height))
self.screen_rect = self.screen.get_rect()
self.text = Settings.text
self.clock = pygame.time.Clock()
self.running = False
self.ticks_per_second = 60
self.time_step = 1.0 / self.ticks_per_second
self.clear_color = Color(35, 0, 30)
self.label_ptext_args = dict(fontname=Settings.gui_font, color=(138,
64, 255))
self.widget_color = Color('white')
# Make the widgets.
self.widget_ptext_args = dict(fontname=Settings.gui_font,
fontsize=Settings.gui_font_size, color='white')
self.widget_color = Color('white')
self.widget_font = 'Roboto_Condensed'
f = ptext.getfont(Settings.gui_font, Settings.gui_font_size)
# Labels
self.labels = make_text_labels(f, Settings.menu_labels)
# Font name, size, fg, bg
x, y = self.labels[0].rect.topright
self.font_name = make_text_menu(f, Settings.font_names, (x + 10, y))
self.font_size = IntThing(Rect((x + 10, self.labels[1].rect.y),
size_ints(f, 1, 36, 1)), 36, 1, 64)
self.fg_color = make_color_menu(f, Color('orange'), (x + 10,
self.labels[2].rect.y))
self.g_color = make_color_menu(f, Color('orange'), (x + 10,
self.labels[3].rect.y))
self.bg_color = make_color_menu(f, Color('black'), (x + 10,
self.labels[4].rect.y))
# Outline color, width
y = self.labels[5].rect.y
self.outline = make_boolean_menu(f, False, (x + 10, y))
self.outline_width = FloatThing(
Rect((self.outline.rect.right + 10, y), size_floats(f, 0.1, 5.0,
0.1, 1)),
1.0, 0.1, 5.0)
self.outline_color = make_color_menu(f, Color('black'),
(self.outline_width.rect.right + 10, y))
# Shadow color, width
y = self.labels[6].rect.y
self.shadow = make_boolean_menu(f, False, (x + 10, y))
self.shadow_x = IntThing(Rect((self.shadow.rect.right + 10, y),
size_ints(f, -9, 9, 1)), 1, -9, 9)
self.shadow_y = IntThing(Rect((self.shadow_x.rect.right, y),
size_ints(f, -9, 9, 1)), 1, -9, 9)
self.shadow_color = make_color_menu(f, Color('black'),
(self.shadow_y.rect.right + 5, y))
# Alpha, Antialias, TextAlign
self.alpha = FloatThing(Rect((x + 10, self.labels[7].rect.y),
size_floats(f, 0, 1, 0.1, 1)), 1, 0, 1, 0.1)
self.antialias = make_boolean_menu(f, True, (x + 10,
self.labels[8].rect.y))
self.textalign = make_text_menu(f, ['center', 'left', 'right'], (x +
10, self.labels[9].rect.y))
# BG Image, scale and fill color
y = self.labels[10].rect.y
self.show_bg = make_text_menu(f, ['show', 'hide'], (x + 10, y))
self.scale_bg = FloatThing(
Rect((self.show_bg.rect.right + 5, y), size_floats(f, 0.2, 10, 0.2,
1)), 1, 0.2, 10, 0.2)
self.fill_color = make_color_menu(f, Color(35, 0, 30),
(self.scale_bg.rect.right + 5, y))
# All the widgets
self.widgets = [
self.font_name, self.font_size, self.fg_color, self.g_color,
self.bg_color,
self.outline, self.outline_width,
self.shadow, self.shadow_x, self.shadow_y, self.shadow_color,
self.alpha, self.antialias, self.textalign, self.show_bg,
self.scale_bg, self.fill_color]
# Subsurface for rendering the text
max_w = reduce(max, [w.rect.right for w in self.widgets]) + 5
x, y, w, h = self.screen_rect
self.text_surf = self.screen.subsurface((max_w, 0, w - max_w, h))
self.text_rect = self.text_surf.get_rect()
# Make a background image
if Settings.bg_image:
self.bg_surface = pygame.image.load(Settings.bg_image)
else:
surf = ptext.getsurf(
'CLOOOKY!', 'CherryCreamSoda', 140, width=self.text_rect.w,
color='skyblue', owidth=0.2, cache=False)
self.bg_surface = pygame.transform.scale(surf, self.text_rect.size)
self.bg_surface_rect =
self.bg_surface.get_rect(center=self.text_rect.center)
r = self.labels[-1].rect
self.text_pos = r.x, r.bottom + 10
self.mouse_rect = Rect(0, 0, 16, 16)
self.mouse_image = pygame.Surface(self.mouse_rect.size)
self.mouse_image.set_alpha(160)
self.mouse_image.set_colorkey(Color('black'))
pygame.draw.line(self.mouse_image, Color('yellow'), (0, 0), (0, 5), 1)
pygame.draw.line(self.mouse_image, Color('yellow'), (0, 0), (5, 0), 1)
pygame.draw.line(self.mouse_image, Color('yellow'), (0, 0), (15, 15), 1)
pygame.mouse.set_visible(False)
self.up_pressed = False
self.down_pressed = False
self.repeat_throttle = 0.0
pygame.display.set_caption('Hover mouse, use wheel or cursor keys to
change settings')
def run(self):
self.running = True
while self.running:
self.clock.tick(self.ticks_per_second)
self.update()
self.draw()
def update(self):
self.do_events()
self.update_key_repeat()
def update_key_repeat(self):
self.repeat_throttle -= self.time_step
if self.repeat_throttle > 0.0:
return
else:
self.repeat_throttle = 1.0 / 7.0
if self.up_pressed:
self.do_scroll('up', pygame.mouse.get_pos())
if self.down_pressed:
self.do_scroll('down', pygame.mouse.get_pos())
def draw(self):
self.screen.fill(self.clear_color)
if self.fill_color.color != self.clear_color:
self.text_surf.fill(self.fill_color.color)
self.draw_bg()
self.draw_labels()
self.draw_widgets()
self.draw_text()
self.draw_frame()
self.screen.blit(self.mouse_image, self.mouse_rect)
pygame.display.flip()
def draw_bg(self):
if self.show_bg.text == 'show':
surf = self.bg_surface
if self.scale_bg.value != 1.0:
surf = pygame.transform.rotozoom(surf, 0, self.scale_bg.value)
rect = surf.get_rect(center=self.text_rect.center)
self.text_surf.blit(surf, rect)
def draw_labels(self):
for label in self.labels:
label.draw(**self.label_ptext_args)
def draw_widgets(self):
for w in self.widgets:
w.draw(**self.widget_ptext_args)
def draw_text(self):
rect = self.text_rect
g_color = self.g_color.color if self.g_color.color !=
self.fg_color.color else None
bg_color = self.bg_color.color if self.bg_color.color != (0, 0, 0) else
None
outline_width = self.outline_width.value if self.outline.value else None
outline_color = self.outline_color.color if self.outline.value else None
shadow = (self.shadow_x.value, self.shadow_y.value) if
self.shadow.value else None
shadow_color = self.shadow_color.color if self.shadow.value else None
ptext.draw(
self.text, self.text_pos, surf=self.text_surf,
fontname=self.font_name.text, color=self.fg_color.color,
gcolor=g_color, background=bg_color,
fontsize=self.font_size.value, centerx=rect.centerx,
centery=rect.centery,
owidth=outline_width, ocolor=outline_color, shadow=shadow,
scolor=shadow_color,
alpha=self.alpha.value, antialias=self.antialias.value,
textalign=self.textalign.text)
def draw_frame(self):
pygame.draw.rect(self.screen, Color('grey'), self.screen_rect, 3)
pygame.draw.rect(self.text_surf, Color('grey'), self.text_rect, 3)
def do_events(self):
for e in pygame.event.get():
if e.type == KEYDOWN:
self.key_down(e)
elif e.type == KEYUP:
self.key_up(e)
elif e.type == MOUSEBUTTONDOWN:
self.mouse_button_down(e)
elif e.type == MOUSEBUTTONUP:
self.mouse_button_up(e)
elif e.type == MOUSEMOTION:
self.mouse_motion(e)
elif e.type == QUIT:
self.quit()
def key_down(self, e):
if e.key == K_UP:
self.up_pressed = True
self.repeat_throttle = 0.0
elif e.key == K_DOWN:
self.down_pressed = True
self.repeat_throttle = 0.0
elif e.key == K_ESCAPE:
self.quit()
def key_up(self, e):
if e.key == K_UP:
self.up_pressed = False
elif e.key == K_DOWN:
self.down_pressed = False
def mouse_button_down(self, e):
pass
def mouse_button_up(self, e):
if e.button == 4:
self.do_scroll('up', e.pos)
elif e.button == 5:
self.do_scroll('down', e.pos)
def do_scroll(self, way, pos):
for menu in self.widgets:
rect = menu.rect
if rect.collidepoint(pos):
menu.scroll(way, pos)
break
def mouse_motion(self, e):
self.mouse_rect.topleft = e.pos
def quit(self):
self.running = False
def parse_args():
"""parse command line args and update global Settings"""
screen_size = [Settings.screen_width, Settings.screen_height]
parser = argparse.ArgumentParser(description='Interactive demo for the
ptext module')
parser.add_argument(
'-b', '--bgimage', dest='bgimage', action='store', default=None,
help='load a custom background image')
parser.add_argument(
'-d', '--fontdir', dest='fontdir', action='store', default='',
help='custom font dir (default={})'.format(Settings.font_dir))
parser.add_argument(
'-g', '--geometry', dest='geometry', action='store',
default=screen_size, type=int, nargs=2, metavar='N',
help='size of window, as -g W H (default={} {})'.format(*screen_size))
if Settings.enable_sysfonts:
parser.add_argument(
'-s', '--sysfonts', dest='sysfonts', action='store_true',
help='use system fonts (default=use files in font dir)')
parser.add_argument(
'-t', '--text', dest='text', default=None, action='store', help='custom
text to display (default=internal)')
parser.add_argument(
'fontfiles', nargs=argparse.REMAINDER, help='list of files in FONTDIR
to use')
args = parser.parse_args()
if args.bgimage:
Settings.bg_image = args.bgimage
if args.geometry:
Settings.screen_width, Settings.screen_height = args.geometry
if args.fontdir:
Settings.font_dir = args.fontdir
if args.text:
Settings.text = args.text
if Settings.enable_sysfonts and args.sysfonts: # unsupported; leave
disabled for now
Settings.use_sysfonts = args.sysfonts
Settings.font_names = pygame.font.get_fonts()
if args.fontfiles:
Settings.font_names = [re.sub(r'\.(ttf|TTF)$', '', s) for s in
args.fontfiles]
if __name__ == '__main__':
parse_args()
pygame.init()
Tweaker().run()