[hamster-applet] sync up with all the goodness that has been going on in experiments (github.com/tbaugis/)



commit 9ea14aedabe1a9f1964841d4341989628ec28d60
Author: Toms Bauģis <toms baugis gmail com>
Date:   Fri Sep 24 23:43:39 2010 +0100

    sync up with all the goodness that has been going on in experiments (github.com/tbaugis/)

 src/hamster/utils/graphics.py |  373 ++++++++++++++++++++++++-----------------
 1 files changed, 217 insertions(+), 156 deletions(-)
---
diff --git a/src/hamster/utils/graphics.py b/src/hamster/utils/graphics.py
index 0ac5af8..d9e3054 100644
--- a/src/hamster/utils/graphics.py
+++ b/src/hamster/utils/graphics.py
@@ -89,14 +89,13 @@ class Graphics(object):
        Most of instructions are mapped to cairo functions by the same name.
        Where there are differences, documenation is provided.
 
-       See http://www.cairographics.org/documentation/pycairo/reference/context.html#class-context
+       See http://cairographics.org/documentation/pycairo/2/reference/context.html
        for detailed description of the cairo drawing functions.
     """
     def __init__(self, context = None):
         self.context = context
         self.colors = Colors    # pointer to the color utilities instance
         self.extents = None     # bounds of the object, only if interactive
-        self.opacity = 1.0      # opacity get's adjusted by parent - TODO - wrong inheritance?
         self.paths = None       # paths for mouse hit checks
         self._last_matrix = None
         self.__new_instructions = deque() # instruction set until it is converted into path-based instructions
@@ -112,41 +111,35 @@ class Graphics(object):
     @staticmethod
     def _stroke(context): context.stroke()
     def stroke(self, color = None, alpha = 1):
-        """stroke the line with given color and opacity"""
         if color or alpha < 1:self.set_color(color, alpha)
         self._add_instruction(self._stroke,)
 
     @staticmethod
     def _fill(context): context.fill()
     def fill(self, color = None, alpha = 1):
-        """fill path with given color and opacity"""
         if color or alpha < 1:self.set_color(color, alpha)
         self._add_instruction(self._fill,)
 
     @staticmethod
     def _stroke_preserve(context): context.stroke_preserve()
     def stroke_preserve(self, color = None, alpha = 1):
-        """same as stroke, only after stroking, don't discard the path"""
         if color or alpha < 1:self.set_color(color, alpha)
         self._add_instruction(self._stroke_preserve,)
 
     @staticmethod
     def _fill_preserve(context): context.fill_preserve()
     def fill_preserve(self, color = None, alpha = 1):
-        """same as fill, only after filling, don't discard the path"""
         if color or alpha < 1:self.set_color(color, alpha)
         self._add_instruction(self._fill_preserve,)
 
     @staticmethod
     def _new_path(context): context.new_path()
     def new_path(self):
-        """discard current path"""
         self._add_instruction(self._new_path,)
 
     @staticmethod
     def _paint(context): context.paint()
     def paint(self):
-        """errrm. paint"""
         self._add_instruction(self._paint,)
 
     @staticmethod
@@ -170,38 +163,32 @@ class Graphics(object):
     @staticmethod
     def _save_context(context): context.save()
     def save_context(self):
-        """change current position"""
         self._add_instruction(self._save_context)
 
     @staticmethod
     def _restore_context(context): context.restore()
     def restore_context(self):
-        """change current position"""
         self._add_instruction(self._restore_context)
 
 
     @staticmethod
     def _translate(context, x, y): context.translate(x, y)
     def translate(self, x, y):
-        """change current position"""
         self._add_instruction(self._translate, x, y)
 
     @staticmethod
     def _rotate(context, radians): context.rotate(radians)
     def rotate(self, radians):
-        """change current position"""
         self._add_instruction(self._rotate, radians)
 
     @staticmethod
     def _move_to(context, x, y): context.move_to(x, y)
     def move_to(self, x, y):
-        """change current position"""
         self._add_instruction(self._move_to, x, y)
 
     @staticmethod
     def _line_to(context, x, y): context.line_to(x, y)
     def line_to(self, x, y = None):
-        """draw line"""
         if y is not None:
             self._add_instruction(self._line_to, x, y)
         elif isinstance(x, list) and y is None:
@@ -212,7 +199,6 @@ class Graphics(object):
     @staticmethod
     def _rel_line_to(context, x, y): context.rel_line_to(x, y)
     def rel_line_to(self, x, y = None):
-        """draw line"""
         if x and y:
             self._add_instruction(self._rel_line_to, x, y)
         elif isinstance(x, list) and y is None:
@@ -224,13 +210,12 @@ class Graphics(object):
     def _curve_to(context, x, y, x2, y2, x3, y3):
         context.curve_to(x, y, x2, y2, x3, y3)
     def curve_to(self, x, y, x2, y2, x3, y3):
-        """draw curve. (x2, y2) is the middle point of the curve"""
+        """draw a curve. (x2, y2) is the middle point of the curve"""
         self._add_instruction(self._curve_to, x, y, x2, y2, x3, y3)
 
     @staticmethod
     def _close_path(context): context.close_path()
     def close_path(self):
-        """connect end with beginning of path"""
         self._add_instruction(self._close_path,)
 
     @staticmethod
@@ -241,7 +226,7 @@ class Graphics(object):
         context.set_dash(dash, dash_offset)
 
     def set_line_style(self, width = None, dash = None, dash_offset = 0):
-        """change the width of the line"""
+        """change width and dash of a line"""
         if width is not None:
             self._add_instruction(self._set_line_width, width)
 
@@ -249,15 +234,18 @@ class Graphics(object):
             self._add_instruction(self._set_dash, dash, dash_offset)
 
     def _set_color(self, context, r, g, b, a):
-        if a * self.opacity >= 1:
-            context.set_source_rgb(r, g, b)
+        if a < 1:
+            context.set_source_rgba(r, g, b, a)
         else:
-            context.set_source_rgba(r, g, b, a * self.opacity)
+            context.set_source_rgb(r, g, b)
 
     def set_color(self, color, alpha = 1):
         """set active color. You can use hex colors like "#aaa", or you can use
         normalized RGB tripplets (where every value is in range 0..1), or
-        you can do the same thing in range 0..65535"""
+        you can do the same thing in range 0..65535.
+        also consider skipping this operation and specify the color on stroke and
+        fill.
+        """
         color = self.colors.parse(color) # parse whatever we have there into a normalized triplet
         if len(color) == 4 and alpha is None:
             alpha = color[3]
@@ -339,6 +327,7 @@ class Graphics(object):
 
 
     def fill_stroke(self, fill = None, stroke = None, line_width = None):
+        """fill and stroke the drawn area in one go"""
         if line_width: self.set_line_style(line_width)
 
         if fill and stroke:
@@ -425,8 +414,15 @@ class Graphics(object):
             self.__new_instructions.append((function, params))
 
 
-    def _draw(self, context, with_extents = False):
+    def _draw(self, context, opacity, with_extents = False):
         """draw accumulated instructions in context"""
+
+        # if we have been moved around, we should update bounds
+        check_extents = with_extents and (context.get_matrix() != self._last_matrix or not self.paths)
+        if check_extents:
+            self.paths = deque()
+            self.extents = None
+
         if self.__new_instructions: #new stuff!
             self.__instruction_cache = deque()
             current_color = None
@@ -458,61 +454,70 @@ class Graphics(object):
                                      self._stroke_preserve,
                                      self._fill_preserve):
                     self.__instruction_cache.append((context.copy_path(),
-                                               current_color,
-                                               current_line,
-                                               instruction, ()))
-                    context.new_path() # reset even on preserve as the instruction will preserve it instead
+                                                     current_color,
+                                                     current_line,
+                                                     instruction, ()))
                     instruction_cache = []
+                    if check_extents:
+                        self._remember_path(context, instruction)
                 else:
                     # the rest are non-special
-                    instruction(context, *args)
                     instruction_cache.append((instruction, args))
 
+                if opacity < 1 and instruction == self._set_color:
+                    self._set_color(context, args[0], args[1], args[2], args[3] * opacity)
+                elif opacity < 1 and instruction == self._paint:
+                    context.paint_with_alpha(opacity)
+                else:
+                    instruction(context, *args) # reset even on preserve as the instruction will preserve it instead
+
+
+            # last one
+            if check_extents and instruction not in (self._fill, self._stroke, self._fill_preserve, self._stroke_preserve):
+                self._remember_path(context, self._fill)
 
+            # also empty the temporary cache that was not met by a stroke at the end
             while instruction_cache: # stroke is missing so we just cache
                 instruction, args = instruction_cache.pop(0)
                 self.__instruction_cache.append((None, None, None, instruction, args))
 
 
-        # if we have been moved around, we should update bounds
-        check_extents = with_extents and (context.get_matrix() != self._last_matrix or not self.paths)
-        if check_extents:
-            self.paths = deque()
-            self.extents = None
-
-        if not self.__instruction_cache:
-            return
-
-        for path, color, line, instruction, args in self.__instruction_cache:
-            if color: self._set_color(context, *color)
-            if line: self._set_line_width(context, *line)
-
-            if path:
-                context.append_path(path)
-                if check_extents:
-                    self._remember_path(context, self._fill)
-
-
-            if instruction:
-                if instruction == self._paint and self.opacity < 1:
-                    context.paint_with_alpha(self.opacity)
-                else:
-                    instruction(context, *args)
+        else:
+            if not self.__instruction_cache:
+                return
 
-        if check_extents and instruction not in (self._fill, self._stroke, self._fill_preserve, self._stroke_preserve):
-            # last one
-            self._remember_path(context, self._fill)
+            for path, color, line, instruction, args in self.__instruction_cache:
+                if color:
+                    if opacity < 1:
+                        self._set_color(context, color[0], color[1], color[2], color[3] * opacity)
+                    else:
+                        self._set_color(context, *color)
+                if line: self._set_line_width(context, *line)
+
+                if path:
+                    context.append_path(path)
+                    if check_extents:
+                        self._remember_path(context, self._fill)
+
+                if instruction:
+                    if instruction == self._paint and opacity < 1:
+                        context.paint_with_alpha(opacity)
+                    else:
+                        instruction(context, *args)
+
+            if check_extents and instruction not in (self._fill, self._stroke, self._fill_preserve, self._stroke_preserve):
+                # last one
+                self._remember_path(context, self._fill)
 
         self._last_matrix = context.get_matrix()
 
 
-    def _draw_as_bitmap(self, context):
+    def _draw_as_bitmap(self, context, opacity):
         """
             instead of caching paths, this function caches the whole drawn thing
             use cache_as_bitmap on sprite to enable this mode
         """
         matrix = context.get_matrix()
-
         if self.__new_instructions or matrix != self._last_matrix:
             if self.__new_instructions:
                 self.__instruction_cache = list(self.__new_instructions)
@@ -529,6 +534,7 @@ class Graphics(object):
             path_end_instructions = (self._new_path, self._stroke, self._fill, self._stroke_preserve, self._fill_preserve)
 
             # measure the path extents so we know the size of surface
+            # also to save some time use the context to paint for the first time
             for instruction, args in self.__instruction_cache:
                 if instruction in path_end_instructions:
                     self._remember_path(context, instruction)
@@ -540,7 +546,13 @@ class Graphics(object):
                     y = args[2] if len(args) > 2 else 0
                     self._rectangle(context, x, y, pixbuf.get_width(), pixbuf.get_height())
 
-                instruction(context, *args)
+                if instruction == self._paint and opacity < 1:
+                    context.paint_with_alpha(opacity)
+                elif instruction == self._set_color and opacity < 1:
+                    self._set_color(context, args[0], args[1], args[2], args[3] * opacity)
+                else:
+                    instruction(context, *args)
+
 
             if instruction not in path_end_instructions: # last one
                 self._remember_path(context, self._fill)
@@ -562,9 +574,8 @@ class Graphics(object):
             context.identity_matrix()
             context.translate(self.extents[0], self.extents[1])
             context.set_source_surface(self.cache_surface)
-
-            if self.opacity < 1:
-                context.paint_with_alpha(self.opacity)
+            if opacity < 1:
+                context.paint_with_alpha(opacity)
             else:
                 context.paint()
             context.restore()
@@ -583,6 +594,8 @@ class Sprite(gtk.Object):
     __gsignals__ = {
         "on-mouse-over": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
         "on-mouse-out": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
+        "on-mouse-down": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)),
+        "on-mouse-up": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)),
         "on-click": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)),
         "on-drag-start": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)),
         "on-drag": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)),
@@ -594,7 +607,8 @@ class Sprite(gtk.Object):
                  rotation = 0, pivot_x = 0, pivot_y = 0,
                  scale_x = 1, scale_y = 1,
                  interactive = False, draggable = False,
-                 z_order = 0, cache_as_bitmap = False, mouse_cursor = None):
+                 z_order = 0, mouse_cursor = None,
+                 cache_as_bitmap = False, snap_to_pixel = True):
         gtk.Object.__init__(self)
 
         #: list of children sprites. Use :func:`add_child` to add sprites
@@ -609,8 +623,11 @@ class Sprite(gtk.Object):
         #: boolean marking if sprite can be automatically dragged
         self.draggable = draggable
 
-        #: relative coordinates of the sprites anchor and rotation point
-        self.pivot_x, self.pivot_y = pivot_x, pivot_y # rotation point in sprite's coordinates
+        #: relative x coordinate of the sprites' rotation point
+        self.pivot_x = pivot_x
+
+        #: relative y coordinates of the sprites' rotation point
+        self.pivot_y = pivot_y
 
         #: sprite opacity
         self.opacity = opacity
@@ -636,11 +653,7 @@ class Sprite(gtk.Object):
         #: drawing order between siblings. The one with the highest z_order will be on top.
         self.z_order = z_order
 
-        #: Whether the sprite should be cached as a bitmap. Default: true
-        #: Generally good when you have many static sprites
-        self.cache_as_bitmap = cache_as_bitmap
-
-        #: mouse-over cursor of the sprite. See :class:`Scene`.mouse_cursor
+        #: mouse-over cursor of the sprite. See :meth:`Scene.mouse_cursor`
         #: for possible values
         self.mouse_cursor = mouse_cursor
 
@@ -652,18 +665,40 @@ class Sprite(gtk.Object):
         #: in on-drag-start to adjust drag point
         self.drag_y = None
 
+        #: Whether the sprite should be cached as a bitmap. Default: true
+        #: Generally good when you have many static sprites
+        self.cache_as_bitmap = cache_as_bitmap
+
+        #: Should the sprite coordinates always rounded to full pixel. Default: true
+        #: Mostly this is good for performance but in some cases that can lead
+        #: to rounding errors in positioning.
+        self.snap_to_pixel = snap_to_pixel
+
         self.__dict__["_sprite_dirty"] = True # flag that indicates that the graphics object of the sprite should be rendered
 
     def __setattr__(self, name, val):
-        if self.__dict__.get(name, "hamster_graphics_no_value_really") != val:
-            if name not in ('x', 'y', 'rotation', 'scale_x', 'scale_y', 'visible'):
-                self.__dict__["_sprite_dirty"] = True
-            if name in ('x', 'y'):
-                val = int(val)
+        if self.__dict__.get(name, "hamster_graphics_no_value_really") == val:
+            return
+        self.__dict__[name] = val
 
-            self.__dict__[name] = val
-            self.redraw()
+        if name not in ('x', 'y', 'rotation', 'scale_x', 'scale_y', 'opacity', 'visible', 'z_order'):
+            self.__dict__["_sprite_dirty"] = True
+
+        if name == 'opacity' and self.__dict__.get("cache_as_bitmap") and self.__dict__.get("graphics"):
+            # invalidating cache for the bitmap version as that paints opacity in the image
+            self.graphics._last_matrix = None
+        elif name == 'interactive' and self.__dict__.get("graphics"):
+            # when suddenly item becomes interactive, it well can be that the extents had not been
+            # calculated
+            self.graphics._last_matrix = None
+        elif name == 'z_order' and self.__dict__.get('parent'):
+            self.parent._sort()
 
+        self.redraw()
+
+    def _sort(self):
+        """sort sprites by z_order"""
+        self.sprites = sorted(self.sprites, key=lambda sprite:sprite.z_order)
 
     def add_child(self, *sprites):
         """Add child sprite. Child will be nested within parent"""
@@ -673,8 +708,8 @@ class Sprite(gtk.Object):
 
             self.sprites.append(sprite)
             sprite.parent = self
+        self._sort()
 
-        self.sprites = sorted(self.sprites, key=lambda sprite:sprite.z_order)
 
     def remove_child(self, *sprites):
         for sprite in sprites:
@@ -720,7 +755,7 @@ class Sprite(gtk.Object):
         if scene and scene._redraw_in_progress == False:
             self.parent.redraw()
 
-    def animate(self, duration = None, easing = None, on_complete = None, on_update = None, delay = None, **kwargs):
+    def animate(self, duration = None, easing = None, on_complete = None, on_update = None, **kwargs):
         """Request paretn Scene to Interpolate attributes using the internal tweener.
            Specify sprite's attributes that need changing.
            `duration` defaults to 0.4 seconds and `easing` to cubic in-out
@@ -732,7 +767,7 @@ class Sprite(gtk.Object):
         """
         scene = self.get_scene()
         if scene:
-            scene.animate(self, duration, easing, on_complete, on_update, delay, **kwargs)
+            scene.animate(self, duration, easing, on_complete, on_update, **kwargs)
 
     def _draw(self, context, opacity = 1):
         if self.visible is False:
@@ -749,23 +784,27 @@ class Sprite(gtk.Object):
             context.save()
 
             if any((self.x, self.y, self.pivot_x, self.pivot_y)):
-                context.translate(self.x + self.pivot_x, self.y + self.pivot_y)
+                if self.snap_to_pixel:
+                    context.translate(int(self.x) + int(self.pivot_x), int(self.y) + int(self.pivot_y))
+                else:
+                    context.translate(self.x + self.pivot_x, self.y + self.pivot_y)
 
             if self.rotation:
                 context.rotate(self.rotation)
 
             if self.pivot_x or self.pivot_y:
-                context.translate(-self.pivot_x, -self.pivot_y)
+                if self.snap_to_pixel:
+                    context.translate(int(-self.pivot_x), int(-self.pivot_y))
+                else:
+                    context.translate(-self.pivot_x, -self.pivot_y)
 
             if self.scale_x != 1 or self.scale_y != 1:
                 context.scale(self.scale_x, self.scale_y)
 
-        self.graphics.opacity = self.opacity * opacity
-
         if self.cache_as_bitmap:
-            self.graphics._draw_as_bitmap(context)
+            self.graphics._draw_as_bitmap(context, self.opacity * opacity)
         else:
-            self.graphics._draw(context, self.interactive or self.draggable)
+            self.graphics._draw(context, self.opacity * opacity, self.interactive or self.draggable)
 
         for sprite in self.sprites:
             sprite._draw(context, self.opacity * opacity)
@@ -775,30 +814,6 @@ class Sprite(gtk.Object):
 
         context.new_path() #forget about us
 
-    def _on_click(self, button_state):
-        self.emit("on-click", button_state)
-        if self.parent and isinstance(self.parent, Sprite):
-            self.parent._on_click(button_state)
-
-    def _on_mouse_over(self):
-        # scene will call us when there is mouse
-        self.emit("on-mouse-over")
-
-    def _on_mouse_out(self):
-        # scene will call us when there is mouse
-        self.emit("on-mouse-out")
-
-    def _on_drag_start(self, event):
-        # scene will call us when there is mouse
-        self.emit("on-drag-start", event)
-
-    def _on_drag(self, event):
-        # scene will call us when there is mouse
-        self.emit("on-drag", event)
-
-    def _on_drag_finish(self, event):
-        # scene will call us when there is mouse
-        self.emit("on-drag-finish", event)
 
 class BitmapSprite(Sprite):
     """Caches given image data in a surface similar to targets, which ensures
@@ -849,7 +864,7 @@ class BitmapSprite(Sprite):
 
 
 class Image(BitmapSprite):
-    """Displays image by path"""
+    """Displays image by path. Currently supports only PNG images."""
     def __init__(self, path, **kwargs):
         BitmapSprite.__init__(self, **kwargs)
 
@@ -1004,7 +1019,11 @@ class Rectangle(Sprite):
         self.connect("on-render", self.on_render)
 
     def on_render(self, sprite):
-        self.graphics.rectangle(0, 0, self.width, self.height, self.corner_radius)
+        x, y = 0, 0
+        if self.snap_to_pixel:
+            x, y = 0.5, 0.5
+
+        self.graphics.rectangle(x, y, self.width, self.height, self.corner_radius)
         self.graphics.fill_stroke(self.fill, self.stroke, self.line_width)
 
 
@@ -1060,7 +1079,8 @@ class Circle(Sprite):
 
     def on_render(self, sprite):
         if self.width == self.height:
-            self.graphics.circle(self.width, self.width / 2.0, self.width / 2.0)
+            radius = self.width / 2.0
+            self.graphics.circle(radius, radius, radius)
         else:
             self.graphics.ellipse(0, 0, self.width, self.height)
 
@@ -1094,7 +1114,8 @@ class Scene(gtk.DrawingArea):
         "on-scroll": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)),
     }
 
-    def __init__(self, interactive = True, framerate = 80):
+    def __init__(self, interactive = True, framerate = 60,
+                       background_color = None, scale = False, keep_aspect = True):
         gtk.DrawingArea.__init__(self)
         if interactive:
             self.set_events(gtk.gdk.POINTER_MOTION_MASK
@@ -1142,6 +1163,9 @@ class Scene(gtk.DrawingArea):
         #: Last known y position of the mouse (set on expose event)
         self.mouse_y = None
 
+        #: Background color of the scene. Use either a string with hex color or an RGB triplet.
+        self.background_color = background_color
+
         #: Mouse cursor appearance.
         #: Replace with your own cursor or set to False to have no cursor.
         #: None will revert back the default behavior
@@ -1161,24 +1185,39 @@ class Scene(gtk.DrawingArea):
         self.__drag_start_x, self.__drag_start_y = None, None
 
         self._mouse_in = False
+        self.__last_cursor = None
 
         self.__drawing_queued = False
-        self.__last_expose_time = dt.datetime.now()
-        self.__last_cursor = None
+        self._redraw_in_progress = True
+
+        #: When specified, upon window resize the content will be scaled
+        #: relative to original window size. Defaults to False.
+        self.scale = scale
+
+        #: Should the stage maintain aspect ratio upon scale if
+        #: :attr:`Scene.scale` is enabled. Defaults to true.
+        self.keep_aspect = keep_aspect
+
+        self._original_width, self._original_height = None,  None
+
 
-        self._redraw_in_progress = False
 
     def add_child(self, *sprites):
-        """Add one or several :class:`graphics.Sprite` sprites to scene """
+        """Add one or several :class:`Sprite` objects to the scene"""
         for sprite in sprites:
             if sprite.parent:
                 sprite.parent.remove_child(sprite)
             self.sprites.append(sprite)
             sprite.parent = self
+        self._sort()
+
+    def _sort(self):
+        """sort sprites by z_order"""
         self.sprites = sorted(self.sprites, key=lambda sprite:sprite.z_order)
 
+
     def remove_child(self, *sprites):
-        """Remove one or several :class:`graphics.Sprite` sprites from scene """
+        """Remove one or several :class:`Sprite` sprites from scene """
         for sprite in sprites:
             self.sprites.remove(sprite)
             sprite.parent = None
@@ -1187,28 +1226,7 @@ class Scene(gtk.DrawingArea):
         """Remove all sprites from scene"""
         self.remove_child(*self.sprites)
 
-
-    def redraw(self):
-        """Queue redraw. The redraw will be performed not more often than
-           the `framerate` allows"""
-        if self.__drawing_queued == False: #if we are moving, then there is a timeout somewhere already
-            self.__drawing_queued = True
-            self._last_frame_time = dt.datetime.now()
-            gobject.timeout_add(1000 / self.framerate, self.__interpolate)
-
-    # animation bits
-    def __interpolate(self):
-        if self.tweener:
-            self.tweener.update((dt.datetime.now() - self._last_frame_time).microseconds / 1000000.0)
-
-        self.__drawing_queued = self.tweener.has_tweens()
-        self._last_frame_time = dt.datetime.now()
-
-        self.queue_draw() # this will trigger do_expose_event when the current events have been flushed
-        return self.__drawing_queued
-
-
-    def animate(self, sprite, duration = None, easing = None, on_complete = None, on_update = None, delay = None, **kwargs):
+    def animate(self, sprite, duration = None, easing = None, on_complete = None, on_update = None, **kwargs):
         """Interpolate attributes of the given object using the internal tweener
            and redrawing scene after every tweener update.
            Specify the sprite and sprite's attributes that need changing.
@@ -1217,6 +1235,7 @@ class Scene(gtk.DrawingArea):
 
            Redraw is requested right after creating the animation.
            Example::
+
              # tween some_sprite to coordinates (50,100) using default duration and easing
              scene.animate(some_sprite, x = 50, y = 100)
         """
@@ -1228,14 +1247,26 @@ class Scene(gtk.DrawingArea):
                                        easing=easing,
                                        on_complete=on_complete,
                                        on_update=on_update,
-                                       delay=delay, **kwargs)
+                                       **kwargs)
         self.redraw()
         return tween
 
 
-    # exposure events
-    def do_configure_event(self, event):
-        self.width, self.height = event.width, event.height
+    def redraw(self):
+        """Queue redraw. The redraw will be performed not more often than
+           the `framerate` allows"""
+        if self.__drawing_queued == False: #if we are moving, then there is a timeout somewhere already
+            self.__drawing_queued = True
+            self._last_frame_time = dt.datetime.now()
+            gobject.timeout_add(1000 / self.framerate, self.__redraw_loop)
+
+    def __redraw_loop(self):
+        """loop until there is nothing more to tween"""
+        self.queue_draw() # this will trigger do_expose_event when the current events have been flushed
+
+        self.__drawing_queued = self.tweener and self.tweener.has_tweens()
+        return self.__drawing_queued
+
 
     def do_expose_event(self, event):
         context = self.window.cairo_create()
@@ -1243,15 +1274,34 @@ class Scene(gtk.DrawingArea):
         # clip to the visible part
         context.rectangle(event.area.x, event.area.y,
                           event.area.width, event.area.height)
+        if self.background_color:
+            color = self.colors.parse(self.background_color)
+            context.set_source_rgb(*color)
+            context.fill_preserve()
         context.clip()
 
-        now = dt.datetime.now()
-        self.fps = 1 / ((now - self.__last_expose_time).microseconds / 1000000.0)
-        self.__last_expose_time = now
+        if self.scale:
+            aspect_x = self.width / self._original_width
+            aspect_y = self.height / self._original_height
+            if self.keep_aspect:
+                aspect_x = aspect_y = min(aspect_x, aspect_y)
+            context.scale(aspect_x, aspect_y)
 
         self.mouse_x, self.mouse_y, mods = self.get_window().get_pointer()
 
         self._redraw_in_progress = True
+
+        # update tweens
+        now = dt.datetime.now()
+        delta = (now - (self._last_frame_time or dt.datetime.now())).microseconds / 1000000.0
+        self._last_frame_time = now
+        if self.tweener:
+            self.tweener.update(delta)
+
+        self.fps = 1 / delta
+
+
+        # start drawing
         self.emit("on-enter-frame", context)
         for sprite in self.sprites:
             sprite._draw(context)
@@ -1261,6 +1311,13 @@ class Scene(gtk.DrawingArea):
         self._redraw_in_progress = False
 
 
+    def do_configure_event(self, event):
+        if self._original_width is None:
+            self._original_width = float(event.width)
+            self._original_height = float(event.height)
+
+        self.width, self.height = event.width, event.height
+
     def all_sprites(self, sprites = None):
         """Returns flat list of the sprite tree for simplified iteration"""
 
@@ -1328,12 +1385,12 @@ class Scene(gtk.DrawingArea):
                     cursor = gtk.gdk.HAND2
 
             if over != self._mouse_sprite:
-                over._on_mouse_over()
+                over.emit("on-mouse-over")
                 self.emit("on-mouse-over", over)
                 self.redraw()
 
         if self._mouse_sprite and self._mouse_sprite != over:
-            self._mouse_sprite._on_mouse_out()
+            self._mouse_sprite.emit("on-mouse-out")
             self.emit("on-mouse-out", self._mouse_sprite)
             self.redraw()
 
@@ -1376,7 +1433,7 @@ class Scene(gtk.DrawingArea):
                 self._drag_sprite.drag_x = x1 - self._drag_sprite.x
                 self._drag_sprite.drag_y = y1 - self._drag_sprite.y
 
-                self._drag_sprite._on_drag_start(event)
+                self._drag_sprite.emit("on-drag-start", event)
                 self.emit("on-drag-start", self._drag_sprite, event)
                 self.redraw()
 
@@ -1397,7 +1454,7 @@ class Scene(gtk.DrawingArea):
 
 
                 self._drag_sprite.x, self._drag_sprite.y = new_x, new_y
-                self._drag_sprite._on_drag(event)
+                self._drag_sprite.emit("on-drag", event)
                 self.emit("on-drag", self._drag_sprite, event)
                 self.redraw()
 
@@ -1421,29 +1478,31 @@ class Scene(gtk.DrawingArea):
 
 
     def __on_button_press(self, area, event):
+        target = self.get_sprite_at_position(event.x, event.y)
         self.__drag_start_x, self.__drag_start_y = event.x, event.y
 
-        self._drag_sprite = self.get_sprite_at_position(event.x, event.y)
+        self._drag_sprite = target
         if self._drag_sprite and self._drag_sprite.draggable == False:
             self._drag_sprite = None
 
+        if target:
+            target.emit("on-mouse-down", event)
         self.emit("on-mouse-down", event)
 
     def __on_button_release(self, area, event):
         # trying to not emit click and drag-finish at the same time
+        target = self.get_sprite_at_position(event.x, event.y)
         click = not self.__drag_started or (event.x - self.__drag_start_x) ** 2 + \
                                            (event.y - self.__drag_start_y) ** 2 < self.drag_distance
         if (click and self.__drag_started == False) or not self._drag_sprite:
-            target = self.get_sprite_at_position(event.x, event.y)
-
             if target:
-                target._on_click(event.state)
+                target.emit("on-click", event)
 
             self.emit("on-click", event, target)
             self.redraw()
 
         if self._drag_sprite:
-            self._drag_sprite._on_drag_finish(event)
+            self._drag_sprite.emit("on-drag-finish", event)
             self.emit("on-drag-finish", self._drag_sprite, event)
             self.redraw()
 
@@ -1452,6 +1511,8 @@ class Scene(gtk.DrawingArea):
 
         self.__drag_started = False
         self.__drag_start_x, self__drag_start_y = None, None
+        if target:
+            target.emit("on-mouse-up", event)
         self.emit("on-mouse-up", event)
 
     def __on_scroll(self, area, event):



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]