Partial DriverPygame.putch() implementation. Add patch for PyGame implementing font.render_glyph(). Add test for PyGame font module. Add special key definitions to DriverPygame.
authorRadek Brich <radek.brich@devl.cz>
Mon, 10 Oct 2011 22:20:59 +0200
changeset 25 f69a1f0382ce
parent 24 b248ef500557
child 26 37745c5abc49
Partial DriverPygame.putch() implementation. Add patch for PyGame implementing font.render_glyph(). Add test for PyGame font module. Add special key definitions to DriverPygame.
extra/pygame_font_render_glyph.patch
tests/pygame_font.py
tuikit/driver_curses.py
tuikit/driver_pygame.py
tuikit/layout.py
--- /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