[billreminder] Updating graphing lib.



commit af1dbadc8fbcc6fe635aa0b8b2c6a089d443741d
Author: Og B. Maciel <ogmaciel gnome org>
Date:   Fri Nov 27 08:46:06 2009 -0500

    Updating graphing lib.

 src/gui/widgets/charting.py  |  104 ++++++------
 src/gui/widgets/graphics.py  |  394 +++++++++++++++++++-----------------------
 src/gui/widgets/pytweener.py |    5 +
 3 files changed, 234 insertions(+), 269 deletions(-)
---
diff --git a/src/gui/widgets/charting.py b/src/gui/widgets/charting.py
index d9c8306..4385600 100644
--- a/src/gui/widgets/charting.py
+++ b/src/gui/widgets/charting.py
@@ -34,7 +34,6 @@ http://projecthamster.wordpress.com/
 """
 
 import gtk
-import gobject
 import cairo, pango
 import copy
 import math
@@ -44,7 +43,7 @@ import time
 import colorsys
 import logging
 
-import graphics, pytweener
+import graphics
 
 
 def size_list(set, target_set):
@@ -100,7 +99,6 @@ class Chart(graphics.Area):
         self.background        = Tripplet-tuple of background color in RGB
         self.chart_background  = Tripplet-tuple of chart background color in RGB
         self.bar_base_color    = Tripplet-tuple of bar color in RGB
-        self.bars_beveled      = Should bars be beveled. 
 
         self.show_scale        = Should we show scale values. See grid_stride!
         self.grid_stride       = Step of grid. If expressed in normalized range
@@ -123,14 +121,13 @@ class Chart(graphics.Area):
         # options
         self.max_bar_width     = args.get("max_bar_width", 500)
         self.legend_width      = args.get("legend_width", 0)
-        self.animate           = args.get("animate", True)
+        self.animation           = args.get("animate", True)
 
         self.background        = args.get("background", None)
         self.chart_background  = args.get("chart_background", None)
         self.bar_base_color    = args.get("bar_base_color", None)
 
         self.grid_stride       = args.get("grid_stride", None)
-        self.bars_beveled      = args.get("bars_beveled", False)
         self.values_on_bars    = args.get("values_on_bars", False)
         self.value_format      = args.get("value_format", "%s")
         self.show_scale        = args.get("show_scale", False)
@@ -140,11 +137,17 @@ class Chart(graphics.Area):
         self.framerate         = args.get("framerate", 60)
 
         # other stuff
-        self.tweener = pytweener.Tweener(0.4, pytweener.Easing.Cubic.easeInOut)
-        self.last_frame_time = None
-        self.moving = False
-        
         self.bars = []
+        self.keys = []
+        self.stack_keys = []
+        
+        self.key_colors = {} # key:color dictionary. if key's missing will grab basecolor
+        self.stack_key_colors = {} # key:color dictionary. if key's missing will grab basecolor
+        
+
+        # use these to mark area where the "real" drawing is going on
+        self.graph_x, self.graph_y = 0, 0
+        self.graph_width, self.graph_height = None, None
         
         
     def get_bar_color(self, index):
@@ -182,34 +185,13 @@ class Chart(graphics.Area):
 
         self._update_targets()
 
-        if self.animate:
-            self.last_frame_time = dt.datetime.now()
-            if not self.moving: #if we are moving, then there is a timeout somewhere already
-                gobject.timeout_add(1000 / self.framerate, self._interpolate)
-        else:
-            self.tweener.update(self.tweener.defaultDuration) # set to end frame
-
-            self.redraw_canvas()
-
-
-    def _interpolate(self):
-        """Internal function to do the math, going from previous set to the
-           new one, and redraw graph"""
-        #this can get called before expose    
-        self.moving = self.tweener.hasTweens()
-
-        if not self.window:
-            self.redraw_canvas()
-            return False
+        if not self.animation:
+            self.tweener.finish()
 
-        time_since_start = (dt.datetime.now() - self.last_frame_time).microseconds / 1000000.0
-        self.tweener.update(time_since_start)
         self.redraw_canvas()
-        self.last_frame_time = dt.datetime.now()
 
-        return self.moving
 
-    def _render(self):
+    def on_expose(self):
         # fill whole area 
         if self.background:
             self.fill_area(0, 0, self.width, self.height, self.background)
@@ -238,15 +220,26 @@ class Chart(graphics.Area):
             return bars
     
         retarget(self.bars, self.data)
+
+
+    def longest_label(self, labels):
+        """returns width of the longest label"""
+        max_extent = 0
+        for label in labels:
+            self.layout.set_text(label)
+            label_w, label_h = self.layout.get_pixel_size()
+            max_extent = max(label_w + 5, max_extent)
+        
+        return max_extent
     
     def draw(self):
         logging.error("OMG OMG, not implemented!!!")
 
 
 class BarChart(Chart):
-    def _render(self):
+    def on_expose(self):
         context = self.context
-        Chart._render(self)
+        Chart.on_expose(self)
         
         # determine graph dimensions
         if self.show_stack_labels:
@@ -284,9 +277,6 @@ class BarChart(Chart):
                                                              self.max_bar_width)
         gap = bar_width * 0.05
         
-        # flip hamster.graphics matrix so we don't think upside down
-        self.set_value_range(y_max = 0, y_min = self.graph_height)
-
         # bars and keys
         max_bar_size = self.graph_height
         #make sure bars don't hit the ceiling
@@ -305,7 +295,7 @@ class BarChart(Chart):
             intended_x = (bar_width * i) + (bar_width - label_w) / 2.0
             
             if not prev_label_end or intended_x > prev_label_end:
-                self.move_to(intended_x, -4)
+                self.context.move_to(intended_x, self.graph_height - 4)
                 context.show_layout(self.layout)
             
                 prev_label_end = intended_x + label_w + 3
@@ -321,20 +311,24 @@ class BarChart(Chart):
                         bar_size = round(max_bar_size * bar.size)
                         bar_start += bar_size
                         
+                        last_color = self.stack_key_colors.get(self.stack_keys[j],
+                                                               self.get_bar_color(j))
                         self.draw_bar(bar_x,
                                       self.graph_height - bar_start,
                                       round(bar_width - (gap * 2)),
                                       bar_size,
-                                      self.get_bar_color(j))
+                                      last_color)
             else:
                 bar_size = round(max_bar_size * self.bars[i].size)
                 bar_start = bar_size
 
+                last_color = self.key_colors.get(self.keys[i],
+                                                  base_color)
                 self.draw_bar(bar_x,
                               self.graph_y + self.graph_height - bar_size,
                               round(bar_width - (gap * 2)),
                               bar_size,
-                              base_color)
+                              last_color)
 
 
             if self.values_on_bars:  # it's either stack labels or values at the end for now
@@ -354,6 +348,13 @@ class BarChart(Chart):
                     label_y = self.graph_y + self.graph_height - bar_start - label_h + 5
                 
                 context.move_to(self.graph_x + (bar_width * i) + (bar_width - label_w) / 2.0, label_y)
+
+                # we are in the bar so make sure that the font color is distinguishable
+                if colorsys.rgb_to_hls(*graphics.Colors.rgb(last_color))[1] < 150:
+                    self.set_color(graphics.Colors.almost_white)
+                else:
+                    self.set_color(graphics.Colors.aluminium[5])        
+
                 context.show_layout(self.layout)
     
                 # values on bars
@@ -462,9 +463,9 @@ class BarChart(Chart):
 
 
 class HorizontalBarChart(Chart):
-    def _render(self):
+    def on_expose(self):
         context = self.context
-        Chart._render(self)
+        Chart.on_expose(self)
         rowcount, keys = len(self.keys), self.keys
         
         # push graph to the right, so it doesn't overlap
@@ -530,7 +531,8 @@ class HorizontalBarChart(Chart):
                         bar_size = round(max_bar_size * bar.size)
                         bar_height = round(bar_width - (gap * 2))
                         
-                        last_color = self.get_bar_color(j)
+                        last_color = self.stack_key_colors.get(self.stack_keys[j],
+                                                               self.get_bar_color(j))
                         self.draw_bar(self.graph_x + bar_start,
                                       bar_y,
                                       bar_size,
@@ -542,8 +544,12 @@ class HorizontalBarChart(Chart):
                 bar_start = bar_size
 
                 bar_height = round(bar_width - (gap * 2))
+
+                last_color = self.key_colors.get(self.keys[i],
+                                                 base_color)
+
                 self.draw_bar(self.graph_x, bar_y, bar_size, bar_height,
-                                                                     base_color)
+                                                                     last_color)
 
             # values on bars
             if self.stack_keys:
@@ -561,9 +567,7 @@ class HorizontalBarChart(Chart):
                 self.set_color(graphics.Colors.aluminium[5])        
             else:
                 # we are in the bar so make sure that the font color is distinguishable
-                # this is a hamster fix
-                # TODO - drop the library bit, we will never be adopted
-                if colorsys.rgb_to_hls(*last_color)[1] < 150:
+                if colorsys.rgb_to_hls(*graphics.Colors.rgb(last_color))[1] < 150:
                     self.set_color(graphics.Colors.almost_white)
                 else:
                     self.set_color(graphics.Colors.aluminium[5])        
@@ -587,9 +591,9 @@ class HorizontalDayChart(Chart):
         self.show()
         self.redraw_canvas()
     
-    def _render(self):
+    def on_expose(self):
         context = self.context
-        Chart._render(self)
+        Chart.on_expose(self)
         rowcount, keys = len(self.keys), self.keys
         
         start_hour = 0
diff --git a/src/gui/widgets/graphics.py b/src/gui/widgets/graphics.py
index b308b95..c7a02a5 100644
--- a/src/gui/widgets/graphics.py
+++ b/src/gui/widgets/graphics.py
@@ -3,11 +3,28 @@ import gtk, gobject
 
 import pango, cairo
 
+import pytweener
+from pytweener import Easing
+
 class Colors(object):
     aluminium = [(238, 238, 236), (211, 215, 207), (186, 189, 182),
                  (136, 138, 133), (85, 87, 83), (46, 52, 54)]
     almost_white = (250, 250, 250)
 
+    @staticmethod
+    def normalize_rgb(color):
+        # turns your average rgb into values with components in range 0..1
+        # if none of the componets are over 1 - will return what it got
+        if color[0] > 1 or color[1] > 0 or color[2] > 0:
+            color = [c / 255.0 for c in color]
+        return color
+    
+    @staticmethod
+    def rgb(color):
+        #return color that has each component in 0..255 range
+        return [c*255 for c in Colors.normalize_rgb(color)]
+        
+
 class Area(gtk.DrawingArea):
     """Abstraction on top of DrawingArea to work specifically with cairo"""
     __gsignals__ = {
@@ -17,170 +34,70 @@ class Area(gtk.DrawingArea):
         "button-release": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT, )),
     }
 
-    def do_configure_event ( self, event ):
-        (self.__width, self.__height) = self.window.get_size()
-        self.queue_draw()
-                    
-    def do_expose_event ( self, event ):
-        self.width, self.height = self.window.get_size()
-        self.context = self.window.cairo_create()
-
-
-        self.context.set_antialias(cairo.ANTIALIAS_NONE)
-        self.context.rectangle(event.area.x, event.area.y,
-                               event.area.width, event.area.height)
-        self.context.clip()
-
-        self.layout = self.context.create_layout()
-        default_font = pango.FontDescription(gtk.Style().font_desc.to_string())
-        default_font.set_size(self.font_size * pango.SCALE)
-        self.layout.set_font_description(default_font)
-        alloc = self.get_allocation()  #x, y, width, height
-        self.width, self.height = alloc.width, alloc.height
-        
-        self.mouse_regions = [] #reset since these can move in each redraw
-        self._render()
-
     def __init__(self):
         gtk.DrawingArea.__init__(self)
         self.set_events(gtk.gdk.EXPOSURE_MASK
-                                 | gtk.gdk.LEAVE_NOTIFY_MASK
-                                 | gtk.gdk.BUTTON_PRESS_MASK
-                                 | gtk.gdk.BUTTON_RELEASE_MASK
-                                 | gtk.gdk.POINTER_MOTION_MASK
-                                 | gtk.gdk.POINTER_MOTION_HINT_MASK)
+                        | gtk.gdk.LEAVE_NOTIFY_MASK
+                        | gtk.gdk.BUTTON_PRESS_MASK
+                        | gtk.gdk.BUTTON_RELEASE_MASK
+                        | gtk.gdk.POINTER_MOTION_MASK
+                        | gtk.gdk.POINTER_MOTION_HINT_MASK)
         self.connect("button_release_event", self.__on_button_release)
         self.connect("motion_notify_event", self.__on_mouse_move)
         self.connect("leave_notify_event", self.__on_mouse_out)
 
-
-        self.context = None
-        self.layout = None
-        self.width = None
-        self.height = None
-        self.value_boundaries = None #x_min, x_max, y_min, y_max
-        
-        self.x_factor, self.y_factor = None, None
-        
         self.font_size = 8
-
-        # use these to mark area where the "real" drawing is going on
-        self.graph_x, self.graph_y = 0, 0
-        self.graph_width, self.graph_height = None, None
-        
         self.mouse_regions = [] #regions of drawing that respond to hovering/clicking
-        self.__prev_mouse_regions = None
 
-    def set_text(self, text):
-        # sets text and returns width and height of the layout
-        self.layout.set_text(text)
-        w, h = self.layout.get_pixel_size()
-        return w, h
-        
+        self.context, self.layout = None, None
+        self.width, self.height = None, None
+        self.__prev_mouse_regions = None
         
-    def set_color(self, color, opacity = None):
-        if color[0] > 1 or color[1] > 0 or color[2] > 0:
-            color = [c / 255.0 for c in color]
+        self.tweener = pytweener.Tweener(0.4, pytweener.Easing.Cubic.easeInOut)
+        self.framerate = 30 #thirty seems to be good enough to avoid flicker
+        self.last_frame_time = None
+        self.__animating = False
+
+    def on_expose(self):
+        """ on_expose event is where you hook in all your drawing
+            canvas has been initialized for you """
+        raise NotImplementedError
 
-        if opacity:
-            self.context.set_source_rgba(color[0], color[1], color[2], opacity)
-        elif len(color) == 3:
-            self.context.set_source_rgb(*color)
-        else:
-            self.context.set_source_rgba(*color)
+    def redraw_canvas(self):
+        """Redraw canvas. Triggers also to do all animations"""
+        if not self.__animating: #if we are moving, then there is a timeout somewhere already
+            self.__animating = True
+            self.last_frame_time = dt.datetime.now()
+            gobject.timeout_add(1000 / self.framerate, self.__interpolate)
+            
+    """ animation bits """
+    def __interpolate(self):
+        self.__animating = self.tweener.hasTweens()
 
+        if not self.window: #will wait until window comes
+            return self.__animating
+        
+        
+        time_since_start = (dt.datetime.now() - self.last_frame_time).microseconds / 1000000.0
+        self.tweener.update(time_since_start)
 
-    def register_mouse_region(self, x1, y1, x2, y2, region_name):
-        self.mouse_regions.append((x1, y1, x2, y2, region_name))
+        self.queue_draw()
+        self.window.process_updates(True)
 
-    def redraw_canvas(self):
-        """Force graph redraw"""
-        if self.window:    #this can get called before expose
-            self.queue_draw()
-            self.window.process_updates(True)
+        self.last_frame_time = dt.datetime.now()
 
+        return self.__animating
 
-    def _render(self):
-        raise NotImplementedError
 
-    def set_value_range(self, x_min = None, x_max = None, y_min = None, y_max = None):
-        """sets up our internal conversion matrix, because cairo one will
-        scale also fonts and we need something in between!"""
-        
-        #store given params, we might redo the math later
-        if not self.value_boundaries:
-            self.value_boundaries = [x_min, x_max, y_min, y_max]
-        else:
-            if x_min != None:
-                self.value_boundaries[0] = x_min
-            if x_max != None:
-                self.value_boundaries[1] = x_max
-            if y_min != None:
-                self.value_boundaries[2] = y_min
-            if y_max != None:
-                self.value_boundaries[3] = y_max 
-        self.x_factor, self.y_factor = None, None
-        self._get_factors()
-
-    def _get_factors(self):
-        if not self.x_factor:
-            self.x_factor = 1
-            if self.value_boundaries and self.value_boundaries[0] != None and self.value_boundaries[1] != None:
-                self.x_factor = float(self.graph_width or self.width) / abs(self.value_boundaries[1] - self.value_boundaries[0])
-                
-        if not self.y_factor:            
-            self.y_factor = 1
-            if self.value_boundaries and self.value_boundaries[2] != None and self.value_boundaries[3] != None:
-                self.y_factor = float(self.graph_height or self.height) / abs(self.value_boundaries[3] - self.value_boundaries[2])
-
-        return self.x_factor, self.y_factor        
-
-
-    def get_pixel(self, x_value = None, y_value = None):
-        """returns screen pixel position for value x and y. Useful to
-        get and then pad something
-
-        x = min1 + (max1 - min1) * (x / abs(max2-min2))  
-            => min1 + const1 * x / const2
-            => const3 = const1 / const2
-            => min + x * const3
-        """
-        x_factor, y_factor = self._get_factors()
-
-        if x_value != None:
-            if self.value_boundaries and self.value_boundaries[0] != None:
-                if self.value_boundaries[1] > self.value_boundaries[0]:
-                    x_value = self.value_boundaries[0] + x_value * x_factor
-                else: #case when min is larger than max (flipped)
-                    x_value = self.value_boundaries[1] - x_value * x_factor
-            if y_value is None:
-                return x_value + self.graph_x
-
-        if y_value != None:
-            if self.value_boundaries and self.value_boundaries[2] != None:
-                if self.value_boundaries[3] > self.value_boundaries[2]:
-                    y_value = self.value_boundaries[2] + y_value * y_factor
-                else: #case when min is larger than max (flipped)
-                    y_value = self.value_boundaries[2] - y_value * y_factor
-            if x_value is None:
-                return y_value + self.graph_y
-            
-        return x_value + self.graph_x, y_value + self.graph_y
+    def animate(self, object, params = {}, duration = None, easing = None, callback = None):
+        if duration: params["tweenTime"] = duration  # if none will fallback to tweener's default
+        if easing: params["tweenType"] = easing    # if none will fallback to tweener's default
+        if callback: params["onCompleteFunction"] = callback
+        self.tweener.addTween(object, **params)
+        self.redraw_canvas()
+    
 
-    def get_value_at_pos(self, x = None, y = None):
-        """returns mapped value at the coordinates x,y"""
-        x_factor, y_factor = self._get_factors()
-        
-        if x != None:
-            x = (x - self.graph_x)  / x_factor
-            if y is None:
-                return x
-        if y != None:
-            y = (y - self.graph_x) / y_factor
-            if x is None:
-                return y
-        return x, y            
-        
+    """ drawing on canvas bits """
     def __rectangle(self, x, y, w, h, color, opacity = 0):
         if color[0] > 1: color = [c / 256.0 for c in color]
 
@@ -202,32 +119,57 @@ class Area(gtk.DrawingArea):
     def fill_rectangle(self, x, y, w, h, color, opacity = 0):
         self.context.save()
         self.__rectangle(x, y, w, h, color, opacity)
-        self.context.fill()
-        self.__rectangle(x, y, w, h, color, 0)
+        self.context.fill_preserve()
+        self.set_color(color)
         self.context.stroke()
         self.context.restore()
 
-    def longest_label(self, labels):
-        """returns width of the longest label"""
-        max_extent = 0
-        for label in labels:
-            self.layout.set_text(label)
-            label_w, label_h = self.layout.get_pixel_size()
-            max_extent = max(label_w + 5, max_extent)
+    def set_text(self, text):
+        # sets text and returns width and height of the layout
+        self.layout.set_text(text)
+        return self.layout.get_pixel_size()
         
-        return max_extent
-    
-    def move_to(self, x, y):
-        """our copy of moveto that takes into account our transformations"""
-        self.context.move_to(*self.get_pixel(x, y))
+    def set_color(self, color, opacity = None):
+        color = Colors.normalize_rgb(color)
+
+        if opacity:
+            self.context.set_source_rgba(color[0], color[1], color[2], opacity)
+        elif len(color) == 3:
+            self.context.set_source_rgb(*color)
+        else:
+            self.context.set_source_rgba(*color)
 
-    def line_to(self, x, y):
-        self.context.line_to(*self.get_pixel(x, y))
+
+    def register_mouse_region(self, x1, y1, x2, y2, region_name):
+        self.mouse_regions.append((x1, y1, x2, y2, region_name))
+
+    """ exposure events """
+    def do_configure_event(self, event):
+        (self.__width, self.__height) = self.window.get_size()
+        self.queue_draw()
+                    
+    def do_expose_event(self, event):
+        self.width, self.height = self.window.get_size()
+        self.context = self.window.cairo_create()
+
+
+        self.context.set_antialias(cairo.ANTIALIAS_NONE)
+        self.context.rectangle(event.area.x, event.area.y,
+                               event.area.width, event.area.height)
+        self.context.clip()
+
+        self.layout = self.context.create_layout()
+        default_font = pango.FontDescription(gtk.Style().font_desc.to_string())
+        default_font.set_size(self.font_size * pango.SCALE)
+        self.layout.set_font_description(default_font)
+        alloc = self.get_allocation()  #x, y, width, height
+        self.width, self.height = alloc.width, alloc.height
         
-    def __on_mouse_out(self, area, event):
-        self.__prev_mouse_regions = None
-        self.emit("mouse-over", [])
+        self.mouse_regions = [] #reset since these can move in each redraw
+        self.on_expose()
+
 
+    """ mouse events """
     def __on_mouse_move(self, area, event):
         if not self.mouse_regions:
             return
@@ -254,6 +196,10 @@ class Area(gtk.DrawingArea):
 
         self.__prev_mouse_regions = mouse_regions
 
+    def __on_mouse_out(self, area, event):
+        self.__prev_mouse_regions = None
+        self.emit("mouse-over", [])
+
     def __on_button_release(self, area, event):
         if not self.mouse_regions:
             return
@@ -270,62 +216,72 @@ class Area(gtk.DrawingArea):
         if mouse_regions:
             self.emit("button-release", mouse_regions)
 
+ 
 
-class Integrator(object):
-    """an iterator, inspired by "visualizing data" book to simplify animation"""
-    def __init__(self, start_value, damping = 0.5, attraction = 0.2):
-        #if we got datetime, convert it to unix time, so we operate with numbers again
-        self.current_value = start_value
-        if isinstance(start_value, dt.datetime):
-            self.current_value = int(time.mktime(start_value.timetuple()))
-            
-        self.value_type = type(start_value)
-
-        self.target_value = start_value
-        self.current_frame = 0
-
-        self.targeting = False
-        self.vel, self.accel, self.force = 0, 0, 0
-        self.mass = 1
-        self.damping = damping
-        self.attraction = attraction
-
-    def __repr__(self):
-        current, target = self.current_value, self.target_value
-        if self.value_type == dt.datetime:
-            current = dt.datetime.fromtimestamp(current)
-            target = dt.datetime.fromtimestamp(target)
-        return "<Integrator %s, %s>" % (current, target)
+
+""" simple example """
+class SimpleAnimation(Area):
+    def __init__(self):
+        Area.__init__(self)
+        self.rect_x, self.rect_y = 10.5, 10.5
+        self.rect_width, self.rect_height = 50, 50
         
-    def target(self, value):
-        """target next value"""
-        self.targeting = True
-        self.target_value = value
-        if isinstance(value, dt.datetime):
-            self.target_value = int(time.mktime(value.timetuple()))
+    def on_expose(self):
+        # on expose is called when we are ready to draw
         
-    def update(self):
-        """goes from current to target value
-        if there is any action needed. returns velocity, which is synonym from
-        delta. Use it to determine when animation is done (experiment to find
-        value that fits you!"""
-
-        if self.targeting:
-            self.force += self.attraction * (self.target_value - self.current_value)
-
-        self.accel = self.force / self.mass
-        self.vel = (self.vel + self.accel) * self.damping
-        self.current_value += self.vel    
-        self.force = 0
-        return abs(self.vel)
-
-    def finish(self):
-        self.current_value = self.target_value
+        # fill_area is just a shortcut function
+        # feel free to use self.context. move_to, line_to and others
+        self.fill_area(self.rect_x,
+                            self.rect_y,
+                            self.rect_width,
+                            self.rect_height, (168, 186, 136))
+
+class BasicWindow:
+    # close the window and quit
+    def delete_event(self, widget, event, data=None):
+        gtk.main_quit()
+        return False
+
+    def __init__(self):
+        # Create a new window
+        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
     
-    @property
-    def value(self):
-        if self.value_type == dt.datetime:
-            return dt.datetime.fromtimestamp(self.current_value)
-        else:
-            return self.current_value
- 
+        self.window.set_title("Graphics Module")
+        self.window.set_size_request(500, 500)
+        self.window.connect("delete_event", self.delete_event)
+    
+        self.graphic = SimpleAnimation()
+        
+        box = gtk.VBox()
+        box.pack_start(self.graphic)
+        
+        button = gtk.Button("Hello")
+        button.connect("clicked", self.on_go_clicked)
+
+        box.add_with_properties(button, "expand", False)
+    
+        self.window.add(box)
+        self.window.show_all()
+
+
+    def on_go_clicked(self, widget):
+        import random
+        
+        # set x and y to random position within the drawing area
+        x = round(min(random.random() * self.graphic.width,
+                      self.graphic.width - self.graphic.rect_width))
+        y = round(min(random.random() * self.graphic.height,
+                      self.graphic.height - self.graphic.rect_height))
+        
+        # here we call the animate function with parameters we would like to change
+        # the easing functions outside graphics module can be accessed via
+        # graphics.Easing
+        self.graphic.animate(self.graphic,
+                             dict(rect_x = x, rect_y = y),
+                             duration = 0.8,
+                             easing = Easing.Elastic.easeOut)
+
+if __name__ == "__main__":
+   example = BasicWindow()
+   gtk.main()
+    
\ No newline at end of file
diff --git a/src/gui/widgets/pytweener.py b/src/gui/widgets/pytweener.py
index 790b88f..113b4a5 100644
--- a/src/gui/widgets/pytweener.py
+++ b/src/gui/widgets/pytweener.py
@@ -90,6 +90,11 @@ class Tweener:
             if t.target is obj:
                 t.complete = True
  
+    def finish(self):
+        #go to last frame for all tweens
+        for t in self.currentTweens:
+            t.update(t.duration)
+        self.currentTweens = []
  
     def update(self, timeSinceLastFrame):
         removable = []



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