Partial DriverPygame.putch() implementation. Add patch for PyGame implementing font.render_glyph(). Add test for PyGame font module. Add special key definitions to DriverPygame.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/extra/pygame_font_render_glyph.patch Mon Oct 10 22:20:59 2011 +0200
@@ -0,0 +1,109 @@
+# Add Font.render_glyph() method to PyGame.
+#
+# This is nicer than full string rendering, as Tuikit renders screen
+# one character at the time and render_glyph is optimized for caching.
+# See documentation for TTF_RenderGlyph_Shaded and TTF_GlyphMetrics.
+#
+# Radek Brich <radek.brich@devl.cz>
+diff -rupd pygame-1.9.1release/src/font.c pygame-1.9.1release-my/src/font.c
+--- pygame-1.9.1release/src/font.c 2009-06-17 01:18:34.000000000 +0200
++++ pygame-1.9.1release-my/src/font.c 2011-10-09 23:19:46.000000000 +0200
+@@ -371,6 +371,78 @@ font_render (PyObject* self, PyObject* a
+ }
+
+ static PyObject*
++font_render_glyph (PyObject* self, PyObject* args)
++{
++ TTF_Font* font = PyFont_AsFont (self);
++ int aa;
++ PyObject* text, *final;
++ PyObject* fg_rgba_obj, *bg_rgba_obj = NULL;
++ Uint8 rgba[4];
++ SDL_Surface* surf;
++ SDL_Color foreg, backg;
++
++ if (!PyArg_ParseTuple (args, "OiO|O", &text, &aa, &fg_rgba_obj,
++ &bg_rgba_obj))
++ return NULL;
++
++ if (!RGBAFromColorObj (fg_rgba_obj, rgba))
++ return RAISE (PyExc_TypeError, "Invalid foreground RGBA argument");
++ foreg.r = rgba[0];
++ foreg.g = rgba[1];
++ foreg.b = rgba[2];
++ if (bg_rgba_obj)
++ {
++ if (!RGBAFromColorObj (bg_rgba_obj, rgba))
++ return RAISE (PyExc_TypeError, "Invalid background RGBA argument");
++ backg.r = rgba[0];
++ backg.g = rgba[1];
++ backg.b = rgba[2];
++ backg.unused = 0;
++ }
++ else
++ {
++ backg.r = 0;
++ backg.g = 0;
++ backg.b = 0;
++ backg.unused = 0;
++ }
++
++ if (PyUnicode_Check (text) && PyObject_Length (text) == 1)
++ {
++ Py_UNICODE *buf = PyUnicode_AsUnicode (text);
++ Uint16 ch = (Uint16) buf[0];
++
++ if (aa)
++ {
++ if (!bg_rgba_obj)
++ surf = TTF_RenderGlyph_Blended (font, ch, foreg);
++ else
++ surf = TTF_RenderGlyph_Shaded (font, ch, foreg, backg);
++ }
++ else
++ surf = TTF_RenderGlyph_Solid (font, ch, foreg);
++ }
++ else
++ return RAISE (PyExc_TypeError, "char must be string of exactly one unicode character");
++
++ if (!surf)
++ return RAISE (PyExc_SDLError, TTF_GetError());
++
++ if (!aa && bg_rgba_obj) /*turn off transparancy*/
++ {
++ SDL_SetColorKey (surf, 0, 0);
++ surf->format->palette->colors[0].r = backg.r;
++ surf->format->palette->colors[0].g = backg.g;
++ surf->format->palette->colors[0].b = backg.b;
++ }
++
++ final = PySurface_New (surf);
++ if (!final)
++ SDL_FreeSurface (surf);
++ return final;
++}
++
++static PyObject*
+ font_size (PyObject* self, PyObject* args)
+ {
+ TTF_Font* font = PyFont_AsFont (self);
+@@ -509,6 +581,7 @@ static PyMethodDef font_methods[] =
+
+ { "metrics", font_metrics, METH_VARARGS, DOC_FONTMETRICS },
+ { "render", font_render, METH_VARARGS, DOC_FONTRENDER },
++ { "render_glyph", font_render_glyph, METH_VARARGS, DOC_FONTRENDERGLYPH },
+ { "size", font_size, METH_VARARGS, DOC_FONTSIZE },
+
+ { NULL, NULL, 0, NULL }
+diff -rupd pygame-1.9.1release/src/pygamedocs.h pygame-1.9.1release-my/src/pygamedocs.h
+--- pygame-1.9.1release/src/pygamedocs.h 2009-06-19 08:20:25.000000000 +0200
++++ pygame-1.9.1release-my/src/pygamedocs.h 2011-10-09 23:27:34.000000000 +0200
+@@ -299,6 +299,8 @@
+
+ #define DOC_FONTRENDER "Font.render(text, antialias, color, background=None): return Surface\ndraw text on a new Surface"
+
++#define DOC_FONTRENDERGLYPH "Font.render_glyph(char, antialias, color, background=None): return Surface\ndraw glyph of char on a new Surface"
++
+ #define DOC_FONTSIZE "Font.size(text): return (width, height)\ndetermine the amount of space needed to render text"
+
+ #define DOC_FONTSETUNDERLINE "Font.set_underline(bool): return None\ncontrol if text is rendered with an underline"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/pygame_font.py Mon Oct 10 22:20:59 2011 +0200
@@ -0,0 +1,29 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+import pygame
+
+pygame.init()
+
+font = pygame.font.SysFont('dejavusansmono', 14)
+
+print('Size: %s %s' % font.size('Q'))
+print('Linesize: %s' % font.get_linesize())
+print('Height: %s' % font.get_height())
+print('Ascent: %s' % font.get_ascent())
+print('Descent: %s' % font.get_descent())
+
+chars = '┌─┐└─┘│'
+metrics = font.metrics(chars)
+print('metrics=(minx, maxx, miny, maxy, advance)')
+
+for c, m in zip(chars, metrics):
+ s = font.size(c)
+ surface = font.render(c, False, (255,255,255), (0,0,0))
+ ss = surface.get_size()
+ ssg = None
+ if hasattr(font, 'render_glyph'):
+ surface_glyph = font.render_glyph(c, False, (255,255,255), (0,0,0))
+ ssg = surface_glyph.get_size()
+ print('%s metrics=%s size=%s surface_size=%s glyph_size=%s' % (c, m, s, ss, ssg))
+
--- a/tuikit/driver_curses.py Sun Oct 09 19:30:25 2011 +0200
+++ b/tuikit/driver_curses.py Mon Oct 10 22:20:59 2011 +0200
@@ -306,7 +306,7 @@
def process_mouse(self):
try:
- id, x, y, z, bstate = curses.getmouse()
+ _id, x, y, _z, bstate = curses.getmouse()
except curses.error:
return []
--- a/tuikit/driver_pygame.py Sun Oct 09 19:30:25 2011 +0200
+++ b/tuikit/driver_pygame.py Mon Oct 10 22:20:59 2011 +0200
@@ -5,7 +5,90 @@
import logging
from tuikit.driver import Driver
-from tuikit.common import Size
+from tuikit.common import Coords, Size
+
+
+class CharCache:
+
+ '''CharCache should implement character cache, as the name suggests.
+
+ Currently it does not, characters ale rendered directly from TTF.
+
+ '''
+
+ def __init__(self):
+ self.font = pygame.font.SysFont('dejavusansmono,liberationmono,freemono', 14)
+ # get advance of some random char (all should be same in monospace font)
+ advance = self.font.metrics('Q')[0][4]
+ height = self.font.get_height()
+ self.charsize = Size(advance, height)
+ self.ascent = self.font.get_ascent()
+
+ # choose self.render() implementation
+ if hasattr(self.font, 'render_glyph'):
+ self.render = self.render_glyph
+ else:
+ self.render = self.render_noglyph
+
+ def render_glyph(self, screen, x, y, c, fgcolor, bgcolor):
+ '''Render using render_glyph and metrics.
+
+ This is the correct way, but the output seems same as of render_noglyph
+ and this implementation requires patching PyGame to work.
+
+ This implements render() method. See render_noglyph for other implementation.
+
+ '''
+ # render character, get metrics
+ surface = self.font.render_glyph(c, True, fgcolor, bgcolor)
+ metrics = self.font.metrics(c)[0]
+ minx, maxx, miny, maxy, advance = metrics
+ height, ascent = self.charsize.h, self.ascent
+
+ # clips origin and area of rendered character according to metrics
+ startx, starty = 0, 0
+ if minx < 0:
+ startx = abs(minx)
+ minx = 0
+ if maxy > ascent:
+ starty = maxy - ascent
+ maxy -= starty
+ if ascent - miny > height:
+ miny = ascent - height
+ if maxx > advance:
+ maxx = advance
+
+ # draw background
+ dest = Coords(x * self.charsize.w, y * self.charsize.h)
+ screen.fill(bgcolor, pygame.Rect(dest.x, dest.y, self.charsize.w, self.charsize.h))
+
+ # draw character
+ dest.x += minx
+ dest.y += ascent - maxy
+ area = pygame.Rect(startx, starty, maxx - minx, maxy - miny)
+ screen.blit(surface, tuple(dest), area)
+
+ def render_noglyph(self, screen, x, y, c, fgcolor, bgcolor):
+ '''Render character using normal text rendering.
+
+ This implements render() method. See render_glyph for other implementation.
+
+ '''
+ # render character, get metrics
+ surface = self.font.render(c, True, fgcolor, bgcolor)
+ metrics = self.font.metrics(c)[0]
+ minx = metrics[0]
+ startx = 0
+ if minx < 0:
+ startx = abs(minx)
+
+ # draw background
+ dest = Coords(x * self.charsize.w, y * self.charsize.h)
+ screen.fill(bgcolor, pygame.Rect(dest.x, dest.y, self.charsize.w, self.charsize.h))
+
+ # draw character
+ area = pygame.Rect(startx, 0, self.charsize.w, self.charsize.h)
+ screen.blit(surface, tuple(dest), area)
class DriverPygame(Driver):
@@ -13,7 +96,35 @@
'''PyGame driver class.'''
key_map = {
- pygame.K_ESCAPE : 'escape',
+ pygame.K_ESCAPE : 'escape',
+ pygame.K_TAB : 'tab',
+ pygame.K_RETURN : 'enter',
+ pygame.K_BACKSPACE : 'backspace',
+ pygame.K_F1 : 'f1',
+ pygame.K_F2 : 'f2',
+ pygame.K_F3 : 'f3',
+ pygame.K_F4 : 'f4',
+ pygame.K_F5 : 'f5',
+ pygame.K_F6 : 'f6',
+ pygame.K_F7 : 'f7',
+ pygame.K_F8 : 'f8',
+ pygame.K_F9 : 'f9',
+ pygame.K_F10 : 'f10',
+ pygame.K_F11 : 'f11',
+ pygame.K_F12 : 'f12',
+ pygame.K_INSERT : 'insert',
+ pygame.K_DELETE : 'delete',
+ pygame.K_HOME : 'home',
+ pygame.K_END : 'end',
+ pygame.K_PAGEUP : 'pageup',
+ pygame.K_PAGEDOWN : 'pagedown',
+ pygame.K_UP : 'up',
+ pygame.K_DOWN : 'down',
+ pygame.K_LEFT : 'left',
+ pygame.K_RIGHT : 'right',
+ pygame.K_PRINT : 'print',
+ pygame.K_SCROLLOCK : 'scrollock',
+ pygame.K_PAUSE : 'pause',
}
color_map = {
@@ -32,11 +143,16 @@
Driver.__init__(self)
self.log = logging.getLogger('tuikit')
self.screen = None
- self.size.w, self.size.h = 80, 25 # screen size in characters
- self.charsize = Size(8, 16) # character size in pixels
+ self.size.w, self.size.h = 120, 40 # screen size in characters
+ self.charcache = None
+ self.charsize = Size(16, 8) # character size in pixels
+
+ self.colorprefix = [] # stack of color prefixes
def start(self, mainfunc):
pygame.init()
+ self.charcache = CharCache()
+ self.charsize = self.charcache.charsize
mode = self.size.w * self.charsize.w, self.size.h * self.charsize.h
self.screen = pygame.display.set_mode(mode)
mainfunc()
@@ -55,10 +171,10 @@
elif ev.type == pygame.MOUSEBUTTONDOWN:
pass
elif ev.type == pygame.KEYDOWN:
- if ev.unicode:
+ if ev.key in self.key_map:
+ events.append(('keypress', self.key_map[ev.key], None))
+ elif ev.unicode:
events.append(('keypress', None, ev.unicode))
- elif ev.key in self.key_map:
- events.append(('keypress', self.key_map[ev.key], None))
elif ev.type == pygame.VIDEORESIZE:
#self.size.h, self.size.w = self.screen.getmaxyx()
events.append(('resize',))
@@ -78,13 +194,9 @@
def putch(self, x, y, c):
if not self.clipstack.test(x, y):
return
-# try:
-# if isinstance(c, str) and len(c) == 1:
-# self.screen.addstr(y, x, c)
-# else:
-# self.screen.addch(y, x, c)
-# except curses.error:
-# pass
+ fgcolor = (255,255,255)
+ bgcolor = (70,70,70)
+ self.charcache.render(self.screen, x, y, c, fgcolor, bgcolor)
def commit(self):
'''Commit changes to the screen.'''
@@ -107,6 +219,12 @@
def popcolor(self):
'''Remove color from top of stack and use new top color for following output.'''
+ def pushcolorprefix(self, name):
+ self.colorprefix.append(name)
+
+ def popcolorprefix(self):
+ self.colorprefix.pop()
+
## cursor ##
--- a/tuikit/layout.py Sun Oct 09 19:30:25 2011 +0200
+++ b/tuikit/layout.py Mon Oct 10 22:20:59 2011 +0200
@@ -104,7 +104,7 @@
for child in self._getchildren():
if coln == 0:
row = []
- for i in range(self.numcols):
+ for _i in range(self.numcols):
row.append({'widget': None, 'colspan': 0, 'rowspan': 0})
colspan = 1