DriverPygame: add colors, make window resizable.
authorRadek Brich <radek.brich@devl.cz>
Wed, 12 Oct 2011 00:58:46 +0200
changeset 27 139d1241b4c5
parent 26 37745c5abc49
child 28 feee783d4fc5
DriverPygame: add colors, make window resizable.
tuikit/driver.py
tuikit/driver_curses.py
tuikit/driver_pygame.py
--- a/tuikit/driver.py	Tue Oct 11 10:09:58 2011 +0200
+++ b/tuikit/driver.py	Wed Oct 12 00:58:46 2011 +0200
@@ -16,9 +16,11 @@
         '''Clipping region stack.'''
         self.unigraph = UnicodeGraphics()
         '''Unicode graphics characters.'''
+        self.colorprefix = []
+        '''Stack of color prefixes.'''
 
 
-    # drawing
+    ## drawing ##
     
     def puts(self, x, y, s):
         '''Output string of characters.'''
@@ -55,4 +57,12 @@
         self.vline(x, y+1, h-2, self.unigraph.VLINE)
         self.vline(x+w-1, y+1, h-2, self.unigraph.VLINE)
 
-    
\ No newline at end of file
+
+    ## colors ##
+    
+    def pushcolorprefix(self, name):
+        self.colorprefix.append(name)
+
+    def popcolorprefix(self):
+        self.colorprefix.pop()
+
--- a/tuikit/driver_curses.py	Tue Oct 11 10:09:58 2011 +0200
+++ b/tuikit/driver_curses.py	Wed Oct 12 00:58:46 2011 +0200
@@ -47,7 +47,7 @@
         (0x1b,0x5b,0x5b,0x45,       'f5'            ),  # linux
     )
 
-    color_names = {
+    color_map = {
         'black'   : curses.COLOR_BLACK,
         'blue'    : curses.COLOR_BLUE,
         'cyan'    : curses.COLOR_CYAN,
@@ -58,6 +58,14 @@
         'yellow'  : curses.COLOR_YELLOW,
     }
 
+    attr_map = {
+        'blink'     : curses.A_BLINK,
+        'bold'      : curses.A_BOLD,
+        'dim'       : curses.A_DIM,
+        'standout'  : curses.A_STANDOUT,
+        'underline' : curses.A_UNDERLINE,
+    }
+
     def __init__(self):
         '''Set driver attributes to default values.'''
         Driver.__init__(self)
@@ -67,7 +75,6 @@
         self.colors = {}     # maps names to curses attributes
         self.colorpairs = {} # maps tuple (fg,bg) to curses color_pair
         self.colorstack = [] # pushcolor/popcolor puts or gets attributes from this
-        self.colorprefix = [] # stack of color prefixes
         self.inputqueue = []
         self.mbtnstack = []
 
@@ -92,7 +99,7 @@
 
     def _parsecolor(self, name):
         name = name.lower().strip()
-        return self.color_names[name]
+        return self.color_map[name]
 
     def _getcolorpair(self, fg, bg):
         pair = (fg, bg)
@@ -107,14 +114,7 @@
         res = 0
         for a in attrs:
             a = a.lower().strip()
-            trans = {
-                'blink'     : curses.A_BLINK,
-                'bold'      : curses.A_BOLD,
-                'dim'       : curses.A_DIM,
-                'standout'  : curses.A_STANDOUT,
-                'underline' : curses.A_UNDERLINE,
-            }
-            res = res | trans[a]
+            res = res | self.attr_map[a]
         return res
 
     def setcolor(self, name, desc):
@@ -128,7 +128,7 @@
         self.colors[name] = curses.color_pair(col) | attr
 
     def pushcolor(self, name):
-        # add prefix if available
+        # add prefix if such color is available
         if len(self.colorprefix):
             prefixname = self.colorprefix[-1] + name
             if prefixname in self.colors:
@@ -144,13 +144,7 @@
         else:
             attr = 0
         self.screen.attrset(attr)
-
-    def pushcolorprefix(self, name):
-        self.colorprefix.append(name)
-
-    def popcolorprefix(self):
-        self.colorprefix.pop()
-
+    
 
     ## drawing ##
 
--- a/tuikit/driver_pygame.py	Tue Oct 11 10:09:58 2011 +0200
+++ b/tuikit/driver_pygame.py	Wed Oct 12 00:58:46 2011 +0200
@@ -17,7 +17,9 @@
     '''
     
     def __init__(self):
-        self.font = pygame.font.SysFont('dejavusansmono,liberationmono,freemono', 14)
+        fontselect = 'dejavusansmono,liberationmono,freemono'
+        self.font = pygame.font.SysFont(fontselect, 14)
+        self.font_bold = pygame.font.SysFont(fontselect, 14, True)
         # 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()
@@ -30,7 +32,7 @@
         else:
             self.render = self.render_noglyph
 
-    def render_glyph(self, screen, x, y, c, fgcolor, bgcolor):
+    def render_glyph(self, screen, x, y, c, fgcolor, bgcolor, attr):
         '''Render using render_glyph and metrics.
         
         This is the correct way, but the output seems same as of render_noglyph
@@ -39,9 +41,14 @@
         This implements render() method. See render_noglyph for other implementation.
         
         '''
+        if attr == 'bold':
+            font = self.font_bold
+        else:
+            font = self.font
+        
         # render character, get metrics
-        surface = self.font.render_glyph(c, True, fgcolor, bgcolor)
-        metrics = self.font.metrics(c)[0]
+        surface = font.render_glyph(c, True, fgcolor, bgcolor)
+        metrics = font.metrics(c)[0]
         minx, maxx, miny, maxy, advance = metrics
         height, ascent = self.charsize.h, self.ascent
         
@@ -68,15 +75,20 @@
         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):
+    def render_noglyph(self, screen, x, y, c, fgcolor, bgcolor, attr):
         '''Render character using normal text rendering.
         
         This implements render() method. See render_glyph for other implementation.
         
-        '''       
+        '''
+        if attr == 'bold':
+            font = self.font_bold
+        else:
+            font = self.font
+               
         # render character, get metrics
-        surface = self.font.render(c, True, fgcolor, bgcolor)
-        metrics = self.font.metrics(c)[0]
+        surface = font.render(c, True, fgcolor, bgcolor)
+        metrics = font.metrics(c)[0]
         minx = metrics[0]
         startx = 0
         if minx < 0:
@@ -95,7 +107,7 @@
     
     '''PyGame driver class.'''
     
-    key_map = {
+    keymap = {
         pygame.K_ESCAPE     : 'escape',
         pygame.K_TAB        : 'tab',
         pygame.K_RETURN     : 'enter',
@@ -127,15 +139,23 @@
         pygame.K_PAUSE      : 'pause',
         }
     
-    color_map = {
-        'black'   : (0,0,0),
-        'blue'    : (0,0,0),
-        'cyan'    : (0,0,0),
-        'green'   : (0,0,0),
-        'magenta' : (0,0,0),
-        'red'     : (0,0,0),
-        'white'   : (0,0,0),
-        'yellow'  : (0,0,0),
+    colormap = {
+        'black'         : (0,0,0),
+        'blue'          : (23,23,178),
+        'green'         : (23,178,23),
+        'cyan'          : (23,178,178),
+        'red'           : (178,23,23),
+        'magenta'       : (178,23,178),
+        'yellow'        : (178,103,23),
+        'white'         : (178,178,178),
+        'intenseblack'  : (104,104,104),
+        'intenseblue'   : (84,84,255),
+        'intensegreen'  : (84,255,84),
+        'intensecyan'   : (84,255,255),
+        'intensered'    : (255,84,84),
+        'intensemagenta': (255,84,255),
+        'intenseyellow' : (255,255,84),
+        'intensewhite'  : (255,255,255),
         }
     
     def __init__(self):
@@ -146,15 +166,19 @@
         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
+        self.last_keypress = None
+        self.last_key = None
+        self.colors = {}     # maps names to curses attributes
+        self.colorstack = [] # pushcolor/popcolor puts or gets attributes from this
+        self.default_color = (self.colormap['white'], self.colormap['black'], 0)
+        self.current_color = self.default_color
 
     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)
+        self.screen = pygame.display.set_mode(mode, pygame.RESIZABLE)
         mainfunc()
 
 
@@ -177,10 +201,11 @@
             # keyboard
             elif ev.type == pygame.KEYDOWN:
                 keypress = self.keypress_from_pygame_event(ev)
-                events.append(keypress)
-                self.last_keypress = keypress
-                self.last_key = ev.key
-                pygame.time.set_timer(pygame.USEREVENT, 200)
+                if keypress:
+                    events.append(keypress)
+                    self.last_keypress = keypress
+                    self.last_key = ev.key
+                    pygame.time.set_timer(pygame.USEREVENT, 200)
             elif ev.type == pygame.USEREVENT: # repeat last key press
                 events.append(self.last_keypress)
                 pygame.time.set_timer(pygame.USEREVENT, 50)
@@ -188,12 +213,14 @@
                 if ev.key == self.last_key:
                     pygame.time.set_timer(pygame.USEREVENT, 0)
             
-            # window resize
+            # window
             elif ev.type == pygame.VIDEORESIZE:
-                #self.size.h, self.size.w = self.screen.getmaxyx()
-                events.append(('resize',))
-            
-            # window close
+                neww, newh = ev.w // self.charsize.w, ev.h // self.charsize.h
+                if neww != self.size.w or newh != self.size.h:
+                    self.size.w, self.size.h = neww, newh
+                    mode = self.size.w * self.charsize.w, self.size.h * self.charsize.h
+                    self.screen = pygame.display.set_mode(mode, pygame.RESIZABLE)
+                    events.append(('resize',))
             elif ev.type == pygame.QUIT:
                 events.append(('quit',))
             
@@ -202,24 +229,26 @@
         return events
 
     def keypress_from_pygame_event(self, ev):
-        if ev.key in self.key_map:
-            keypress = ('keypress', self.key_map[ev.key], None)
+        if ev.key in self.keymap:
+            keypress = ('keypress', self.keymap[ev.key], None)
         elif ev.unicode:
             keypress = ('keypress', None, ev.unicode)
+        else:
+            self.log.debug('Unknown key: key=%r unicode=%r' % (ev.key, ev.unicode))
+            keypress = None
         return keypress
 
     ## drawing ##
     
     def erase(self):
         '''Clear screen.'''
-        self.screen.fill(self.color_map['black'])
+        self.screen.fill(self.colormap['black'])
     
     def putch(self, x, y, c):
         if not self.clipstack.test(x, y):
             return
-        fgcolor = (255,255,255)
-        bgcolor = (70,70,70)
-        self.charcache.render(self.screen, x, y, c, fgcolor, bgcolor)
+        fgcolor, bgcolor, attr = self.current_color
+        self.charcache.render(self.screen, x, y, c, fgcolor, bgcolor, attr)
 
     def commit(self):
         '''Commit changes to the screen.'''
@@ -228,6 +257,20 @@
 
     ## colors ##
     
+    def _parsecolor(self, name, attr=None):
+        name = name.lower().strip()
+        if attr == 'bold':
+            name = 'intense' + name
+        return self.colormap[name]
+    
+    def _parseattrs(self, attrs):
+        res = ''
+        for a in attrs:
+            a = a.lower().strip()
+            if a == 'bold':
+                res = a
+        return res
+
     def setcolor(self, name, desc):
         '''Define color name.
         
@@ -235,18 +278,33 @@
         desc - color description - foreground, background, attributes (e.g. 'black on white, bold')
         
         '''
+        parts = desc.split(',')
+        fg, bg = parts[0].split(' on ')
+        attrs = parts[1:]
+        attr = self._parseattrs(attrs)
+        fg = self._parsecolor(fg, attr)
+        bg = self._parsecolor(bg)
+        self.colors[name] = (fg, bg, attr)
 
     def pushcolor(self, name):
         '''Add color on top of stack and use this color for following output.'''
+        # add prefix if such color is available
+        if len(self.colorprefix):
+            prefixname = self.colorprefix[-1] + name
+            if prefixname in self.colors:
+                name = prefixname
+        col = self.colors[name]
+        self.current_color = col
+        self.colorstack.append(col)
     
     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()
+        self.colorstack.pop()
+        if len(self.colorstack):
+            col = self.colorstack[-1]
+        else:
+            col = self.default_color
+        self.current_color = col
 
 
     ## cursor ##