[Sugar-devel] [PATCH TypingTurtle 1/2] Port to Cairo

Manuel Kaufmann humitos at gmail.com
Thu Aug 2 09:45:15 EDT 2012


This will ease the port to GTK+ 3

Signed-off-by: Manuel Kaufmann <humitos at gmail.com>
---
 balloongame.py | 151 ++++++++++++++++++++++++++++------------------
 keyboard.py    | 187 +++++++++++++++++++++++++++------------------------------
 titlescene.py  |  44 ++++++++------
 3 files changed, 206 insertions(+), 176 deletions(-)

diff --git a/balloongame.py b/balloongame.py
index 56a6a34..7224893 100644
--- a/balloongame.py
+++ b/balloongame.py
@@ -14,7 +14,10 @@
 # You should have received a copy of the GNU General Public License
 # along with Typing Turtle.  If not, see <http://www.gnu.org/licenses/>.
 
+import math
 import random, datetime
+import pangocairo
+
 from gettext import gettext as _
 
 import gobject, pygtk, gtk, pango
@@ -192,28 +195,35 @@ class BalloonGame(gtk.VBox):
  
         return True
 
-    def draw_results(self, gc):
+    def draw_results(self, cr):
         # Draw background.
         w = self.bounds.width - 400
         h = self.bounds.height - 200
         x = self.bounds.width/2 - w/2
         y = self.bounds.height/2 - h/2
 
-        gc.foreground = self.area.get_colormap().alloc_color(50000,50000,50000)
-        self.area.window.draw_rectangle(gc, True, x, y, w, h)
-        gc.foreground = self.area.get_colormap().alloc_color(0,0,0)
-        self.area.window.draw_rectangle(gc, False, x, y, w, h)
+        cr.set_source_rgb(0.762, 0.762, 0.762)
+        cr.rectangle(x, y, w, h)
+        cr.fill()
 
-        # Draw text
-        gc.foreground = self.area.get_colormap().alloc_color(0,0,0)
+        cr.set_source_rgb(0, 0, 0)
+        cr.rectangle(x, y, w, h)
+        cr.stroke()
 
+        # Draw text
         title = _('You finished!') + '\n'
-        layout = self.area.create_pango_layout(title)
-        layout.set_font_description(pango.FontDescription('Serif Bold 16'))    
-        size = layout.get_size()
-        tx = x+w/2-(size[0]/pango.SCALE)/2
+
+        pango_cr = pangocairo.CairoContext(cr)
+        pango_cr.set_source_rgb(0, 0, 0)
+        pango_layout = cr.create_layout()
+        pango_layout.set_font_description(pango.FontDescription('Serif Bold 16'))
+        pango_layout.set_text(title)
+        size = pango_layout.get_size()
+        tx = x + (w / 2) - (size[0] / pango.SCALE) / 2
         ty = y + 100
-        self.area.window.draw_layout(gc, tx, ty, layout)
+        pango_cr.move_to(tx, ty)
+        pango_cr.show_layout(pango_layout)
+        pango_cr.stroke()
 
         report = ''
         report += _('Your score was %(score)d.') % { 'score': self.score } + '\n'
@@ -222,12 +232,18 @@ class BalloonGame(gtk.VBox):
         report += '\n'
         report += _('Press the ENTER key to continue.')
     
-        layout = self.area.create_pango_layout(report)
-        layout.set_font_description(pango.FontDescription('Times 12'))    
-        size = layout.get_size()
-        tx = x+w/2-(size[0]/pango.SCALE)/2
-        ty = y + 200
-        self.area.window.draw_layout(gc, tx, ty, layout)
+        pango_cr = pangocairo.CairoContext(cr)
+        pango_cr.set_source_rgb(0, 0, 0)
+        pango_layout = cr.create_layout()
+        pango_layout.set_font_description(pango.FontDescription('Times 12'))
+        pango_layout.set_text(report)
+        size = pango_layout.get_size()
+        sx = x + w / 2 - (size[0] / pango.SCALE) / 2
+        sy = y + 200
+        pango_cr.move_to(sx, sy)
+        pango_cr.show_layout(pango_layout)
+        pango_cr.stroke()
+
 
     def finish_game(self):
         self.finished = True
@@ -290,29 +306,35 @@ class BalloonGame(gtk.VBox):
         h = int(b.size*1.5 + 10)
         self.area.queue_draw_area(x, y, w, h)
 
-    def draw_balloon(self, gc, b):
+    def draw_balloon(self, cr, b):
         x = int(b.x)
         y = int(b.y)
-        
+
         # Draw the string.
-        gc.foreground = self.area.get_colormap().alloc_color(0,0,0)
-        self.area.window.draw_line(gc, 
-            int(b.x), int(b.y+b.size/2), 
-            int(b.x), int(b.y+b.size))
-        
+        cr.set_source_rgb(0, 0, 0)
+        cr.move_to(int(b.x), int(b.y + b.size / 2))
+        cr.line_to(int(b.x), int(b.y + b.size))
+        cr.stroke()
+
         # Draw the balloon.
-        gc.foreground = self.area.get_colormap().alloc_color(b.color[0],b.color[1],b.color[2])
-        self.area.window.draw_arc(gc, True, x-b.size/2, y-b.size/2, b.size, b.size, 0, 360*64)
-    
-        # Draw the text.
-        gc.foreground = self.area.get_colormap().alloc_color(0,0,0)
-        layout = self.area.create_pango_layout(b.word)
-        layout.set_font_description(pango.FontDescription('Sans 12'))    
-        size = layout.get_size()
-        tx = x-(size[0]/pango.SCALE)/2
-        ty = y-(size[1]/pango.SCALE)/2
-        self.area.window.draw_layout(gc, tx, ty, layout)
-    
+        cr.save()
+        cr.set_source_rgb(b.color[0], b.color[1], b.color[2])
+        cr.arc(b.x, b.y, b.size / 2, 0, 2 * math.pi)
+        cr.fill()
+        cr.restore()
+
+        pango_cr = pangocairo.CairoContext(cr)
+        pango_cr.set_source_rgb(0, 0, 0)
+        pango_layout = cr.create_layout()
+        pango_layout.set_font_description(pango.FontDescription('Sans 12'))
+        pango_layout.set_text(unicode(b.word))
+        size = pango_layout.get_size()
+        x = x - (size[0] / pango.SCALE) / 2
+        y = y - (size[1] / pango.SCALE) / 2
+        pango_cr.move_to(x, y)
+        pango_cr.show_layout(pango_layout)
+        pango_cr.stroke()
+
     def add_score(self, num):
         self.score += num
         self.queue_draw_score()
@@ -325,45 +347,54 @@ class BalloonGame(gtk.VBox):
         y = 20
         self.queue_draw_area(x, y, x+size[0], y+size[1])
 
-    def draw_score(self, gc):
-        layout = self.area.create_pango_layout(_('SCORE: %d') % self.score)
-        layout.set_font_description(pango.FontDescription('Times 14'))    
-        size = layout.get_size()
-        x = self.bounds.width-20-size[0]/pango.SCALE
+    def draw_score(self, cr):
+        pango_cr = pangocairo.CairoContext(cr)
+        pango_cr.set_source_rgb(0, 0, 0)
+        pango_layout = cr.create_layout()
+        pango_layout.set_font_description(pango.FontDescription('Times 14'))
+        pango_layout.set_text(_('SCORE: %d') % self.score)
+        size = pango_layout.get_size()
+        x = self.bounds.width - 20 - size[0] / pango.SCALE
         y = 20
-        self.area.window.draw_layout(gc, x, y, layout)
+        pango_cr.move_to(x, y)
+        pango_cr.show_layout(pango_layout)
+        pango_cr.stroke()
 
-    def draw_instructions(self, gc):
+    def draw_instructions(self, cr):
         # Draw instructions.
-        gc.foreground = self.area.get_colormap().alloc_color(0,0,0)
-
-        layout = self.area.create_pango_layout(_('Type the words to pop the balloons!'))
-        layout.set_font_description(pango.FontDescription('Times 14'))    
-        size = layout.get_size()
-        x = (self.bounds.width - size[0]/pango.SCALE)/2
-        y = self.bounds.height-20 - size[1]/pango.SCALE 
-        self.area.window.draw_layout(gc, x, y, layout)
+        pango_cr = pangocairo.CairoContext(cr)
+        pango_cr.set_source_rgb(0, 0, 0)
+        pango_layout = cr.create_layout()
+        pango_layout.set_font_description(pango.FontDescription('Times 14'))
+        pango_layout.set_text(_('Type the words to pop the balloons!'))
+        size = pango_layout.get_size()
+        x = (self.bounds.width - size[0] / pango.SCALE) / 2
+        y = self.bounds.height - 20 - size[1] / pango.SCALE
+        pango_cr.move_to(x, y)
+        pango_cr.show_layout(pango_layout)
+        pango_cr.stroke()
 
     def draw(self):
         self.bounds = self.area.get_allocation()
 
-        gc = self.area.window.new_gc()
-        
+        cr = self.area.window.cairo_create()
+
         # Draw background.
-        gc.foreground = self.area.get_colormap().alloc_color(60000,60000,65535)
-        self.area.window.draw_rectangle(gc, True, 0, 0, self.bounds.width, self.bounds.height)
+        cr.set_source_rgb(0.915, 0.915, 1)
+        cr.rectangle(0, 0, self.bounds.width, self.bounds.height)
+        cr.fill()
 
         # Draw the balloons.
         for b in self.balloons:
-            self.draw_balloon(gc, b)
+            self.draw_balloon(cr, b)
 
         if self.finished:
-            self.draw_results(gc)
+            self.draw_results(cr)
 
         else:
-            self.draw_instructions(gc)
+            self.draw_instructions(cr)
 
-            self.draw_score(gc)
+            self.draw_score(cr)
 
     def expose_cb(self, area, event):
         self.draw()
diff --git a/keyboard.py b/keyboard.py
index 35daeed..25e870e 100644
--- a/keyboard.py
+++ b/keyboard.py
@@ -16,12 +16,14 @@
 #!/usr/bin/env python
 # vi:sw=4 et 
 
-import pygtk
-pygtk.require('2.0')
 import gtk
+import cairo
+import copy
 import rsvg
 import os, glob, re
 import pango
+import pangocairo
+import StringIO
 from port import json
 import subprocess
 from layouts.olpc import OLPC_LAYOUT
@@ -130,8 +132,7 @@ class KeyboardImages:
             scale_width = int(scale_width * 1.1625)
 
         for filename in glob.iglob('images/OLPC*.svg'):
-            image = gtk.gdk.pixbuf_new_from_file_at_scale(filename, scale_width,
-                                                          self.height, False)
+            image = rsvg.Handle(file=filename)
             name = os.path.basename(filename)
             self.images[name] = image
 
@@ -383,30 +384,27 @@ class KeyboardWidget(KeyboardData, gtk.DrawingArea):
             k['key-width'] = int(k['key-width'] * width_scale)
             k['key-height'] = int(k['key-height'] * height_scale)
 
-        self._make_all_key_images()
-
-    def _make_key_images(self, key):
-        key['key-images'] = {}
-        for group in [0, 1]:
-            for state in [0, gtk.gdk.SHIFT_MASK, gtk.gdk.MOD5_MASK, gtk.gdk.SHIFT_MASK|gtk.gdk.MOD5_MASK]:
-                key['key-images'][(state, group)] = self.get_key_image(key, state, group)
+    def _draw_key(self, k, cr):
+        bounds = self.get_allocation()
 
-    def _make_all_key_images(self):
-        for key in self.keys:
-            self._make_key_images(key)
+        # HACK: this is a hack used when the widget is not shown yet,
+        # in that case bounds will be gtk.gdk.Rectangle(-1, -1, 1, 1)
+        # and the key will be outside the canvas. This is used only
+        # for the first key that appears below the instructions
+        if bounds.x == -1:
+            screen_x = screen_y = 0
+        else:
+            screen_x = int(bounds.width - self.image.width) / 2
+            screen_y = int(bounds.height - self.image.height) / 2
 
-    def _draw_key(self, k, draw, gc, for_pixmap, w=0, h=0):
-        x1 = 0 
-        y1 = 0
-        x2 = w
-        y2 = h
+        x1 = k['key-x'] + screen_x
+        y1 = k['key-y'] + screen_y
+        x2 = x1 + k['key-width']
+        y2 = y1 + k['key-height']
 
-        # Outline rounded box.
-        gc.foreground = self.get_colormap().alloc_color(int(0.4*65536),int(0.7*65536),int(0.4*65536))
-        
         corner = 5
         points = [
-            (x1 + corner, y1), 
+            (x1 + corner, y1),
             (x2 - corner, y1),
             (x2, y1 + corner),
             (x2, y2 - corner),
@@ -414,26 +412,40 @@ class KeyboardWidget(KeyboardData, gtk.DrawingArea):
             (x1 + corner, y2),
             (x1, y2 - corner),
             (x1, y1 + corner)
-        ]
-        draw.draw_polygon(gc, True, points)
-        
-        # Inner text.
-        gc.foreground = self.get_colormap().alloc_color(int(1.0*65536),int(1.0*65536),int(1.0*65536))
+            ]
+
+        cr.save()
+        cr.new_path()
+        cr.set_source_rgb(0.396, 0.698, 0.392)
+        cr.set_line_width(2)
+        cr.move_to(*points[0])
+        for point in points:
+            cr.line_to(*point)
+        cr.line_to(*points[0])
+        cr.close_path()
+        cr.fill_preserve()
+        cr.stroke()
+        cr.restore()
 
         text = ''
         if k['key-label']:
             text = k['key-label']
         else:
-            text = self.get_letter_for_key_state_group(k, self.active_state, self.active_group)
-        
-        try:
-            layout = self.create_pango_layout(unicode(text))
-            layout.set_font_description(pango.FontDescription('Monospace'))
-            draw.draw_layout(gc, x1+8, y2-23, layout)
-        except:
-            pass
-
-    def _expose_hands(self, gc):
+            text = self.get_letter_for_key_state_group(
+                k, self.active_state, self.active_group)
+
+        pango_context = pangocairo.CairoContext(cr)
+        pango_context.set_source_rgb(0, 0, 0)
+
+        pango_layout = pango_context.create_layout()
+        pango_layout.set_font_description(pango.FontDescription('Monospace'))
+        pango_layout.set_text(unicode(text))
+
+        pango_context.move_to(x1 + 8, y2 - 23)
+        pango_context.show_layout(pango_layout)
+        cr.stroke()
+
+    def _expose_hands(self, cr):
         lhand_image = self.image.images['OLPC_Lhand_HOMEROW.svg']
         rhand_image = self.image.images['OLPC_Rhand_HOMEROW.svg']
 
@@ -459,39 +471,32 @@ class KeyboardWidget(KeyboardData, gtk.DrawingArea):
 
                 # TODO: Do something about ALTGR.
 
-        bounds = self.get_allocation()
-        screen_x = int(bounds.width-self.image.width)/2
-        screen_y = int(bounds.height-self.image.height)/2
+        # bounds = self.get_allocation()
+        # screen_x = int(bounds.width-self.image.width)/2
+        # screen_y = int(bounds.height-self.image.height)/2
+
+        # README: these values (cairo.Matrix) are taken seeing the image on the
+        # screen, I think we should find a way to calculate them
+        cr.save()
+        matrix = cairo.Matrix(xx=0.3, yy=0.2, x0=10, y0=-20)
+        cr.transform(matrix)
+        lhand_image.render_cairo(cr)
 
-        self.window.draw_pixbuf(gc, lhand_image, 0, 0, screen_x, screen_y + HAND_YOFFSET)
-        self.window.draw_pixbuf(gc, rhand_image, 0, 0, screen_x, screen_y + HAND_YOFFSET)
+        cr.restore()
+        matrix = cairo.Matrix(xx=0.325, yy=0.2, x0=-5, y0=-20)
+        cr.transform(matrix)
+        rhand_image.render_cairo(cr)
 
     def _expose_cb(self, area, event):
-        gc = self.window.new_gc()
-        
-        bounds = self.get_allocation()
-        screen_x = int(bounds.width-self.image.width)/2
-        screen_y = int(bounds.height-self.image.height)/2
+        cr = self.window.cairo_create()
 
         # Draw the keys.
         for k in self.keys:
-            x1 = k['key-x'] + screen_x
-            y1 = k['key-y'] + screen_y
-            x2 = x1 + k['key-width']
-            y2 = y1 + k['key-height']
-
-            # Index cached key images by state and group.
-            state = self.active_state & (gtk.gdk.SHIFT_MASK|gtk.gdk.MOD5_MASK)
-            index = (state, self.active_group)
-            image = k['key-images'].get(index)
-
-            if image:
-                self.window.draw_image(gc, image, 0, 0, x1, y1, x2-x1, y2-y1) 
-        
+            self._draw_key(k, cr)
+
         # Draw overlay images.
         if self.draw_hands:
-            self._expose_hands(gc)
-        
+            self._expose_hands(cr)
         return True
 
     def key_press_release_cb(self, widget, event):
@@ -512,14 +517,10 @@ class KeyboardWidget(KeyboardData, gtk.DrawingArea):
             sig = self.format_key_sig(event.hardware_keycode, event.state, event.group)
             if not self.letter_map.has_key(sig):
                 self.letter_map[sig] = event.string
-                self._make_key_images(key)
                 self.queue_draw()
 
         return False
 
-    def _keys_changed_cb(self, keymap):
-        self._make_key_images()
-
     def clear_hilite(self):
         self.hilite_letter = None
         self.queue_draw()
@@ -535,43 +536,31 @@ class KeyboardWidget(KeyboardData, gtk.DrawingArea):
     def get_key_pixbuf(self, key, state=0, group=0, scale=1):
         w = int(key['key-width'] * scale)
         h = int(key['key-height'] * scale)
-        
+
         old_state, old_group = self.active_state, self.active_group
         self.active_state, self.active_group = state, group
-        
-        pixmap = gtk.gdk.Pixmap(self.root_window.window, w, h)
-        gc = pixmap.new_gc()
-        
-        gc.foreground = self.get_colormap().alloc_color('#d0d0d0')
-        pixmap.draw_rectangle(gc, True, 0, 0, w, h)
 
-        self._draw_key(key, pixmap, gc, True, w, h)
-        
-        pb = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, w, h)
-        pb.get_from_drawable(pixmap, self.root_window.window.get_colormap(), 0, 0, 0, 0,w, h)
-        
-        self.active_state, self.active_group = old_state, old_group
+        surface = cairo.ImageSurface(cairo.FORMAT_RGB24, w, h)
+        cr = cairo.Context(surface)
+        cr.set_source_rgb(1, 1, 1)
+        cr.rectangle(0, 0, w, h)
+        cr.fill()
 
-        return pb
+        # Duplicate the Key to be able to change its position values
+        key = copy.deepcopy(key)
+        key['key-x'] = 0
+        key['key-y'] = 0
 
-    def get_key_image(self, key, state=0, group=0, scale=1):
-        w = int(key['key-width'] * scale)
-        h = int(key['key-height'] * scale)
-        
-        old_state, old_group = self.active_state, self.active_group
-        self.active_state, self.active_group = state, group
-        
-        pixmap = gtk.gdk.Pixmap(self.root_window.window, w, h)
-        gc = pixmap.new_gc()
-        
-        gc.foreground = self.get_colormap().alloc_color('#d0d0d0')
-        pixmap.draw_rectangle(gc, True, 0, 0, w, h)
+        self._draw_key(key, cr)
+
+        # Convert cairo.Surface to Pixbuf
+        pixbuf_data = StringIO.StringIO()
+        surface.write_to_png(pixbuf_data)
+        pxb_loader = gtk.gdk.PixbufLoader(image_type='png')
+        pxb_loader.write(pixbuf_data.getvalue())
+        temp_pix = pxb_loader.get_pixbuf()
+        pxb_loader.close()
 
-        self._draw_key(key, pixmap, gc, True, w, h)
-        
-        image = pixmap.get_image(0, 0, w, h)
-        
         self.active_state, self.active_group = old_state, old_group
 
-        return image
-    
+        return temp_pix
diff --git a/titlescene.py b/titlescene.py
index 7cc2d68..4dc6ae4 100644
--- a/titlescene.py
+++ b/titlescene.py
@@ -20,13 +20,15 @@ from gettext import gettext as _
 
 # Import PyGTK.
 import gobject, pygtk, gtk, pango
+import pangocairo
+
 
 class TitleScene(gtk.DrawingArea):
     # Maximum portion of the screen the background can fill vertically.
     BACKGROUND_HEIGHT_RATIO = 0.6
 
     # Border from top right of screen to draw title at.
-    TITLE_OFFSET = (20, 30)
+    TITLE_OFFSET = (20, 50)
 
     # Font used to display the title.
     TITLE_FONT = 'Times 45'
@@ -52,30 +54,38 @@ class TitleScene(gtk.DrawingArea):
 
     def expose_cb(self, area, event):
         bounds = self.get_allocation()
-        
-        gc = self.get_style().fg_gc[gtk.STATE_NORMAL]
+
+        cr = self.window.cairo_create()
 
         # Background picture.
         x = (bounds.width - self.backgroundpixbuf.get_width())/2
-        self.window.draw_pixbuf(
-            gc, self.backgroundpixbuf, 0, 0, 
-            x, 0, self.backgroundpixbuf.get_width(), self.backgroundpixbuf.get_height())
-        pc = self.create_pango_context()
-        
-        self.layout = self.create_pango_layout('')
-        self.layout.set_font_description(pango.FontDescription(TitleScene.TITLE_FONT))
-        
-        self.layout.set_text(self.title_original)
-        original_size = self.layout.get_size()
-        self.x_text = (bounds.width-original_size[0]/pango.SCALE)-TitleScene.TITLE_OFFSET[0]
+        cr.set_source_pixbuf(self.backgroundpixbuf, 0, 0)
+        cr.rectangle(x, 0, self.backgroundpixbuf.get_width(),
+                     self.backgroundpixbuf.get_height())
+        cr.paint()
+
+        cr = pangocairo.CairoContext(cr)
+        cr.set_source_rgb(0, 0, 0)
+        self.pango_layout = cr.create_layout()
+        self.pango_layout.set_font_description(
+            pango.FontDescription(TitleScene.TITLE_FONT))
+        self.pango_layout.set_text(unicode(self.title_original))
+
+        original_size = self.pango_layout.get_size()
+        self.x_text = (bounds.width - original_size[0] / pango.SCALE) - \
+            TitleScene.TITLE_OFFSET[0]
         self.y_text = TitleScene.TITLE_OFFSET[1]
+
         gobject.timeout_add(50, self.timer_cb)
 
     def draw_text(self):
         # Animated Typing Turtle title.
-        gc = self.get_style().fg_gc[gtk.STATE_NORMAL]
-        self.layout.set_text(self.title_text)
-        self.window.draw_layout(gc, self.x_text, self.y_text, self.layout)
+        cr = self.window.cairo_create()
+
+        cr.move_to(self.x_text, self.y_text)
+        self.pango_layout.set_text(unicode(self.title_text))
+        cr.show_layout(self.pango_layout)
+        cr.stroke()
 
     def timer_cb(self):
         if len(self.title_src) > 0:
-- 
1.7.11.2



More information about the Sugar-devel mailing list