[billreminder/fresh] synced with hamster



commit b95d9fbe1b7f2f4acc95a906ce28d73eb5d81692
Author: Toms Bauģis <toms baugis gmail com>
Date:   Wed Jan 20 14:50:29 2010 +0000

    synced with hamster

 src/gui/widgets/charting.py  |  307 +++++++++++++++++----------
 src/gui/widgets/graphics.py  |  131 ++++++++----
 src/gui/widgets/pytweener.py |  494 +++++++++++++++++-------------------------
 3 files changed, 483 insertions(+), 449 deletions(-)
---
diff --git a/src/gui/widgets/charting.py b/src/gui/widgets/charting.py
index cd8f14c..24ecd60 100644
--- a/src/gui/widgets/charting.py
+++ b/src/gui/widgets/charting.py
@@ -75,16 +75,16 @@ def get_limits(set, stack_subfactors = True):
                 min_value = max(row, min_value)
 
     return min_value, max_value
-    
+
 
 class Bar(object):
     def __init__(self, value, size = 0):
         self.value = value
         self.size = size
-    
+
     def __repr__(self):
         return str((self.value, self.size))
-        
+
 
 class Chart(graphics.Area):
     """Chart constructor. Optional arguments:
@@ -146,34 +146,34 @@ class Chart(graphics.Area):
         self.keys = []
         self.data = None
         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
-        
+
         self.mouse_bar = None
         if self.interactive:
             self.connect("mouse-over", self.on_mouse_over)
             self.connect("button-release", self.on_clicked)
-            
+
         self.bars_selected = []
-        
-    
+
+
     def on_mouse_over(self, area, region):
         if region:
             self.mouse_bar = int(region[0])
         else:
             self.mouse_bar = None
-            
+
         self.redraw_canvas()
-        
+
     def on_clicked(self, area, bar):
         self.emit("bar-clicked", self.mouse_bar)
-    
+
     def select_bar(self, index):
         pass
 
@@ -181,15 +181,15 @@ class Chart(graphics.Area):
         # returns color darkened by it's index
         # the approach reduces contrast by each step
         base_color = self.bar_base_color or (220, 220, 220)
-        
+
         base_hls = colorsys.rgb_to_hls(*base_color)
-        
+
         step = (base_hls[1] - 30) / 10 #will go from base down to 20 and max 22 steps
-        
+
         return colorsys.hls_to_rgb(base_hls[0],
                                    base_hls[1] - step * index,
                                    base_hls[2])
-        
+
 
     def draw_bar(self, x, y, w, h, color = None):
         """ draws a simple bar"""
@@ -219,15 +219,15 @@ class Chart(graphics.Area):
 
 
     def on_expose(self):
-        # fill whole area 
+        # fill whole area
         if self.background:
             self.fill_area(0, 0, self.width, self.height, self.background)
-        
+
 
     def _update_targets(self):
         # calculates new factors and then updates existing set
         max_value = float(self.max_value) or 1 # avoid division by zero
-        
+
         self.bars = size_list(self.bars, self.data)
 
         #need function to go recursive
@@ -240,12 +240,11 @@ class Chart(graphics.Area):
                         bars[i] = Bar(new_values[i], 0)
                     else:
                         bars[i].value = new_values[i]
-                        for tween in self.tweener.getTweensAffectingObject(bars[i]):
-                            self.tweener.removeTween(tween)
+                        self.tweener.killTweensOf(bars[i])
 
                     self.tweener.addTween(bars[i], size = bars[i].value / float(max_value))
             return bars
-    
+
         retarget(self.bars, self.data)
 
 
@@ -256,9 +255,9 @@ class Chart(graphics.Area):
             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!!!")
 
@@ -270,7 +269,7 @@ class BarChart(Chart):
         if not self.data:
             return
 
-        context = self.context        
+        context = self.context
         context.set_line_width(1)
 
 
@@ -282,7 +281,7 @@ class BarChart(Chart):
                 grid_stride = int(self.max_value * self.grid_stride)
             else:
                 grid_stride = int(self.grid_stride)
-            
+
             scale_labels = [self.value_format % i
                   for i in range(grid_stride, int(self.max_value), grid_stride)]
             self.legend_width = legend_width = self.legend_width or self.longest_label(scale_labels)
@@ -321,32 +320,49 @@ class BarChart(Chart):
         bar_width = min(self.graph_width / float(len(self.keys)), self.max_bar_width)
         for i, key in enumerate(self.keys):
             exes[key] = (x + self.graph_x, round(bar_width - 1))
-            
+
             x = x + round(bar_width)
             bar_width = min(self.max_bar_width,
                             (self.graph_width - x) / float(max(1, len(self.keys) - i - 1)))
 
 
+        # now for the text - we want reduced contrast for relaxed visuals
+        fg_color = self.get_style().fg[gtk.STATE_NORMAL].to_string()
+        if self.colors.is_light(fg_color):
+            label_color = self.colors.darker(fg_color,  80)
+        else:
+            label_color = self.colors.darker(fg_color,  -80)
+
+
         for key, bar, data in zip(self.keys, self.bars, self.data):
-            self.set_color(graphics.Colors.aluminium[5]);
+            self.set_color(label_color);
             self.layout.set_text(key)
             label_w, label_h = self.layout.get_pixel_size()
 
             intended_x = exes[key][0] + (exes[key][1] - label_w) / 2
-            
+
             if not prev_label_end or intended_x > prev_label_end:
                 self.context.move_to(intended_x, self.graph_height + 4)
                 context.show_layout(self.layout)
-            
+
                 prev_label_end = intended_x + label_w + 3
-                
+
 
             bar_start = 0
-            base_color = self.bar_base_color or (220, 220, 220)
-            
+
+            # determine bar color
+            base_color = self.bar_base_color
+            if not base_color: #yay, we can be theme friendly!
+                bg_color = self.get_style().bg[gtk.STATE_NORMAL].to_string()
+                if self.colors.is_light(bg_color):
+                    base_color = self.colors.darker(bg_color,  30)
+                else:
+                    base_color = self.colors.darker(bg_color,  -30)
+                    tick_color = self.colors.darker(bg_color,  -50)
+
             if self.stack_keys:
                 remaining_fractions, remaining_pixels = 1, max_bar_size
-                
+
                 for j, stack_bar in enumerate(bar):
                     if stack_bar.size > 0:
                         bar_size = round(remaining_pixels * (stack_bar.size / remaining_fractions))
@@ -354,7 +370,7 @@ class BarChart(Chart):
                         remaining_pixels -= bar_size
 
                         bar_start += bar_size
-                        
+
                         last_color = self.stack_key_colors.get(self.stack_keys[j]) or self.get_bar_color(j)
                         self.draw_bar(exes[key][0],
                                       self.graph_height - bar_start,
@@ -378,30 +394,35 @@ class BarChart(Chart):
                     total_value = sum(data[i])
                 else:
                     total_value = data[i]
-                
+
                 self.layout.set_width(-1)
                 self.layout.set_text(self.value_format % total_value)
                 label_w, label_h = self.layout.get_pixel_size()
-    
+
 
                 if bar_start > label_h + 2:
                     label_y = self.graph_y + self.graph_height - bar_start + 5
                 else:
                     label_y = self.graph_y + self.graph_height - bar_start - label_h + 5
-                
+
                 context.move_to(self.exes[key][0] + (self.exes[key][1] - 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)
+                if self.colors.is_light(last_color):
+                    self.set_color(label_color)
                 else:
-                    self.set_color(graphics.Colors.aluminium[5])        
+                    self.set_color(self.colors.almost_white)
 
                 context.show_layout(self.layout)
 
 
         #white grid and scale values
+        if self.background:
+            grid_color = self.background
+        else:
+            grid_color = self.get_style().bg[gtk.STATE_NORMAL].to_string()
+            
         self.layout.set_width(-1)
         if self.grid_stride and self.max_value:
             # if grid stride is less than 1 then we consider it to be percentage
@@ -409,7 +430,7 @@ class BarChart(Chart):
                 grid_stride = int(self.max_value * self.grid_stride)
             else:
                 grid_stride = int(self.grid_stride)
-            
+
             context.set_line_width(1)
             for i in range(grid_stride, int(self.max_value), grid_stride):
                 y = round(max_bar_size * (i / self.max_value)) + 0.5
@@ -419,10 +440,10 @@ class BarChart(Chart):
                     label_w, label_h = self.layout.get_pixel_size()
                     context.move_to(legend_width - label_w - 8,
                                     y - label_h / 2)
-                    self.set_color(graphics.Colors.aluminium[4])
+                    self.set_color(self.colors.aluminium[4])
                     context.show_layout(self.layout)
 
-                self.set_color("#ffffff")
+                self.set_color(grid_color)
                 self.context.move_to(legend_width, y)
                 self.context.line_to(self.width, y)
 
@@ -430,17 +451,17 @@ class BarChart(Chart):
         #stack keys
         if self.show_stack_labels:
             #put series keys
-            self.set_color(graphics.Colors.aluminium[5]);
-            
+            self.set_color(label_color);
+
             y = self.graph_height
             label_y = None
 
-            # if labels are at end, then we need show them for the last bar! 
+            # if labels are at end, then we need show them for the last bar!
             if self.labels_at_end:
                 factors = self.bars[-1]
             else:
                 factors = self.bars[0]
-            
+
             if isinstance(factors, Bar):
                 factors = [factors]
 
@@ -450,28 +471,28 @@ class BarChart(Chart):
                 self.layout.set_alignment(pango.ALIGN_LEFT)
             else:
                 self.layout.set_alignment(pango.ALIGN_RIGHT)
-    
+
             for j in range(len(factors)):
                 factor = factors[j].size
                 bar_size = factor * max_bar_size
-                
+
                 if round(bar_size) > 0 and self.stack_keys:
                     label = "%s" % self.stack_keys[j]
 
-                    
+
                     self.layout.set_text(label)
                     label_w, label_h = self.layout.get_pixel_size()
-                    
+
                     y -= bar_size
                     intended_position = round(y + (bar_size - label_h) / 2)
-                    
+
                     if label_y:
                         label_y = min(intended_position, label_y - label_h)
                     else:
                         label_y = intended_position
-                    
+
                     if self.labels_at_end:
-                        label_x = self.graph_x + self.graph_width 
+                        label_x = self.graph_x + self.graph_width
                         line_x1 = self.graph_x + self.graph_width - 1
                         line_x2 = self.graph_x + self.graph_width - 6
                     else:
@@ -499,13 +520,13 @@ class HorizontalBarChart(Chart):
 
         context = self.context
         rowcount, keys = len(self.keys), self.keys
-        
+
         # push graph to the right, so it doesn't overlap
         legend_width = self.legend_width or self.longest_label(keys)
-        
+
         self.graph_x = legend_width
         self.graph_x += 8 #add another 8 pixes of padding
-        
+
         self.graph_width = self.width - self.graph_x
         self.graph_y, self.graph_height = 0, self.height
 
@@ -513,7 +534,7 @@ class HorizontalBarChart(Chart):
         if self.chart_background:
             self.fill_area(self.graph_x, self.graph_y, self.graph_width, self.graph_height, self.chart_background)
 
-    
+
         if not self.data:  # go home if we have nothing
             return
 
@@ -522,7 +543,7 @@ class HorizontalBarChart(Chart):
         bar_width = min(self.graph_height / float(len(self.keys)), self.max_bar_width)
         for i, key in enumerate(self.keys):
             positions[key] = (y + self.graph_y, round(bar_width - 1))
-            
+
             y = y + round(bar_width)
             bar_width = min(self.max_bar_width,
                             (self.graph_height - y) / float(max(1, len(self.keys) - i - 1)))
@@ -532,14 +553,34 @@ class HorizontalBarChart(Chart):
 
         self.layout.set_alignment(pango.ALIGN_RIGHT)
         self.layout.set_ellipsize(pango.ELLIPSIZE_END)
-        
 
-        
+
+
         context.set_line_width(1)
 
         # bars and labels
         self.layout.set_width(legend_width * pango.SCALE)
-        
+
+
+        # determine bar color
+        base_color = self.bar_base_color
+        if not base_color: #yay, we can be theme friendly!
+            bg_color = self.get_style().bg[gtk.STATE_NORMAL].to_string()
+            if self.colors.is_light(bg_color):
+                base_color = self.colors.darker(bg_color,  30)
+            else:
+                base_color = self.colors.darker(bg_color,  -30)
+                tick_color = self.colors.darker(bg_color,  -50)
+        last_color = base_color
+
+
+        # now for the text - we want reduced contrast for relaxed visuals
+        fg_color = self.get_style().fg[gtk.STATE_NORMAL].to_string()
+        if self.colors.is_light(fg_color):
+            label_color = self.colors.darker(fg_color,  80)
+        else:
+            label_color = self.colors.darker(fg_color,  -80)
+
 
         for i, label in enumerate(keys):
             if self.interactive:
@@ -552,20 +593,23 @@ class HorizontalBarChart(Chart):
             self.layout.set_width(legend_width * pango.SCALE)
             self.layout.set_text(label)
             label_w, label_h = self.layout.get_pixel_size()
-            
-            self.set_color(graphics.Colors.aluminium[5])        
-            context.move_to(0, positions[label][0] + (positions[label][1] - label_h) / 2)
-            context.show_layout(self.layout)
+            label_y = positions[label][0] + (positions[label][1] - label_h) / 2
 
-            base_color = self.bar_base_color or (220, 220, 220)
+            if i == self.mouse_bar:
+                self.set_color(self.get_style().fg[gtk.STATE_PRELIGHT])
+            else:
+                self.set_color(label_color)
+
+
+            context.move_to(0, label_y)
+            context.show_layout(self.layout)
 
-            last_color = (255,255,255)
 
             if self.stack_keys:
                 bar_start = 0
 
                 remaining_fractions, remaining_pixels = 1, max_bar_size
-                
+
                 for j, stack_bar in enumerate(self.bars[i]):
                     if stack_bar.size > 0:
                         bar_size = round(remaining_pixels * (stack_bar.size / remaining_fractions))
@@ -586,7 +630,7 @@ class HorizontalBarChart(Chart):
                 if i in self.bars_selected:
                     last_color = self.get_style().bg[gtk.STATE_SELECTED].to_string()
                 elif i == self.mouse_bar:
-                    last_color = self.get_style().bg[gtk.STATE_PRELIGHT].to_string()
+                    last_color = self.get_style().base[gtk.STATE_PRELIGHT].to_string()
                 else:
                     last_color = self.key_colors.get(self.keys[i]) or base_color
 
@@ -596,33 +640,48 @@ class HorizontalBarChart(Chart):
                               positions[label][1],
                               last_color)
 
-            # values on bars
+
+            # value labels
             if self.stack_keys:
                 total_value = sum(self.data[i])
             else:
                 total_value = self.data[i]
-            
+
             self.layout.set_width(-1)
             self.layout.set_text(self.value_format % total_value)
             label_w, label_h = self.layout.get_pixel_size()
 
             vertical_padding = max((positions[label][1] - label_h) / 2.0, 1)
+
+            label_y = positions[label][0] + (positions[label][1] - label_h) / 2.0
             if  bar_start - vertical_padding < label_w:
                 label_x = self.graph_x + bar_start + vertical_padding
-                self.set_color(graphics.Colors.aluminium[5])        
+
+                # avoid zero selected bars without any hints
+                if not self.stack_keys and i in self.bars_selected and self.bars[i].value == 0:
+                    self.set_color(self.get_style().bg[gtk.STATE_SELECTED])
+                    self.draw_rect(label_x - 2,
+                                   label_y - 2,
+                                   label_w + 4,
+                                   label_h + 4, 4)
+                    self.context.fill()
+                    self.set_color(self.get_style().fg[gtk.STATE_SELECTED])
+                else:
+                    self.set_color(label_color)
             else:
+                label_x = self.graph_x + bar_start - label_w - vertical_padding
+
                 if i in self.bars_selected:
                     self.set_color(self.get_style().fg[gtk.STATE_SELECTED].to_string())
                 else:
                     # 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)
+                    if self.colors.is_light(last_color):
+                        self.set_color(label_color)
                     else:
-                        self.set_color(graphics.Colors.aluminium[5])        
-                    
-                label_x = self.graph_x + bar_start - label_w - vertical_padding
-            
-            context.move_to(label_x, positions[label][0] + (positions[label][1] - label_h) / 2.0)
+                        self.set_color(self.colors.almost_white)
+
+
+            context.move_to(label_x, label_y)
             context.show_layout(self.layout)
 
         context.stroke()
@@ -636,39 +695,39 @@ class HorizontalDayChart(Chart):
     def __init__(self, *args, **kwargs):
         Chart.__init__(self, *args, **kwargs)
         self.start_time, self.end_time = None, None
-    
+
     def plot_day(self, keys, data, start_time = None, end_time = None):
         self.keys, self.data = keys, data
         self.start_time, self.end_time = start_time, end_time
         self.show()
         self.redraw_canvas()
-    
+
     def on_expose(self):
         context = self.context
-        
+
         Chart.on_expose(self)
         rowcount, keys = len(self.keys), self.keys
-        
+
         start_hour = 0
         if self.start_time:
             start_hour = self.start_time
-        end_hour = 24 * 60        
+        end_hour = 24 * 60
         if self.end_time:
             end_hour = self.end_time
-        
-        
+
+
         # push graph to the right, so it doesn't overlap
         legend_width = self.legend_width or self.longest_label(keys)
 
         self.graph_x = legend_width
         self.graph_x += 8 #add another 8 pixes of padding
-        
+
         self.graph_width = self.width - self.graph_x
-        
+
         #on the botttom leave some space for label
         self.layout.set_text("1234567890:")
         label_w, label_h = self.layout.get_pixel_size()
-        
+
         self.graph_y, self.graph_height = 0, self.height - label_h - 4
 
 
@@ -678,47 +737,64 @@ class HorizontalDayChart(Chart):
         if not self.data:  #if we have nothing, let's go home
             return
 
-        
+
         positions = {}
         y = 0
         bar_width = min(self.graph_height / float(len(self.keys)), self.max_bar_width)
         for i, key in enumerate(self.keys):
             positions[key] = (y + self.graph_y, round(bar_width - 1))
-            
+
             y = y + round(bar_width)
             bar_width = min(self.max_bar_width,
                             (self.graph_height - y) / float(max(1, len(self.keys) - i - 1)))
 
 
-        
+
         max_bar_size = self.graph_width - 15
 
+
+        # now for the text - we want reduced contrast for relaxed visuals
+        fg_color = self.get_style().fg[gtk.STATE_NORMAL].to_string()
+        if self.colors.is_light(fg_color):
+            label_color = self.colors.darker(fg_color,  80)
+        else:
+            label_color = self.colors.darker(fg_color,  -80)
+
+
         self.layout.set_alignment(pango.ALIGN_RIGHT)
         self.layout.set_ellipsize(pango.ELLIPSIZE_END)
-        
+
         # bars and labels
         self.layout.set_width(legend_width * pango.SCALE)
 
         factor = max_bar_size / float(end_hour - start_hour)
 
+        # determine bar color
+        base_color = self.bar_base_color
+        if not base_color: #yay, we can be theme friendly!
+            bg_color = self.get_style().bg[gtk.STATE_NORMAL].to_string()
+            if self.colors.is_light(bg_color):
+                base_color = self.colors.darker(bg_color,  30)
+            else:
+                base_color = self.colors.darker(bg_color,  -30)
+                tick_color = self.colors.darker(bg_color,  -50)
+
         for i, label in enumerate(keys):
-            self.set_color(graphics.Colors.aluminium[5])        
-            
+            self.set_color(label_color)
+
             self.layout.set_text(label)
             label_w, label_h = self.layout.get_pixel_size()
 
             context.move_to(0, positions[label][0] + (positions[label][1] - label_h) / 2)
             context.show_layout(self.layout)
 
-            base_color = self.bar_base_color or [220, 220, 220]
-
             if isinstance(self.data[i], list) == False:
                 self.data[i] = [self.data[i]]
-            
+
             for row in self.data[i]:
                 bar_x = round((row[0]- start_hour) * factor)
                 bar_size = round((row[1] - start_hour) * factor - bar_x)
-                
+
                 self.draw_bar(round(self.graph_x + bar_x),
                               positions[label][0],
                               bar_size,
@@ -732,9 +808,16 @@ class HorizontalDayChart(Chart):
 
         pace = ((end_hour - start_hour) / 3) / 60 * 60
         last_position = positions[keys[-1]]
+        
+        
+        if self.background:
+            grid_color = self.background
+        else:
+            grid_color = self.get_style().bg[gtk.STATE_NORMAL].to_string()
+        
         for i in range(start_hour + 60, end_hour, pace):
             x = round((i - start_hour) * factor)
-            
+
             minutes = i % (24 * 60)
 
             self.layout.set_markup(dt.time(minutes / 60, minutes % 60).strftime("%H<small><sup>%M</sup></small>"))
@@ -742,16 +825,16 @@ class HorizontalDayChart(Chart):
 
             context.move_to(self.graph_x + x - label_w / 2,
                             last_position[0] + last_position[1] + 4)
-            self.set_color(graphics.Colors.aluminium[4])
+            self.set_color(self.colors.aluminium[4])
             context.show_layout(self.layout)
 
-            
-            self.set_color((255, 255, 255))
+
+            self.set_color(grid_color)
             self.context.move_to(round(self.graph_x + x) + 0.5, self.graph_y)
             self.context.line_to(round(self.graph_x + x) + 0.5,
                                  last_position[0] + last_position[1])
 
-                
+
         context.stroke()
 
 
@@ -762,12 +845,12 @@ class BasicWindow:
         window.set_title("Hamster Charting")
         window.set_size_request(400, 300)
         window.connect("delete_event", lambda *args: gtk.main_quit())
-    
+
         self.stacked = BarChart(background = "#fafafa",
                                 bar_base_color = (220, 220, 220),
                                 legend_width = 20,
                                 show_stack_labels = True)
-        
+
         box = gtk.VBox()
         box.pack_start(self.stacked)
 
@@ -778,7 +861,7 @@ class BasicWindow:
 
         import random
         self.data = [[random.randint(0, 10) for j in range(len(self.stacks))] for i in range(len(self.series))]
-        
+
         color_buttons = gtk.HBox()
         color_buttons.set_spacing(4)
 
@@ -788,13 +871,13 @@ class BasicWindow:
             color_buttons.pack_start(button)
 
         box.pack_start(color_buttons, False)
-        
+
         window.add(box)
         window.show_all()
 
         self.plot()
-        
-        
+
+
     def plot(self):
         self.stacked.stack_key_colors = self.stack_colors
         self.stacked.plot(self.series, self.data, self.stacks)
diff --git a/src/gui/widgets/graphics.py b/src/gui/widgets/graphics.py
index 6897e04..ff9330b 100644
--- a/src/gui/widgets/graphics.py
+++ b/src/gui/widgets/graphics.py
@@ -24,14 +24,16 @@ import pango, cairo
 
 import pytweener
 from pytweener import Easing
+import colorsys
 
 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 color(color):
+    def parse(self, color):
+        assert color is not None
+        
         #parse color into rgb values
         if isinstance(color, str) or isinstance(color, unicode):
             color = gtk.gdk.Color(color)
@@ -44,10 +46,19 @@ class Colors(object):
                 color = [c / 255.0 for c in color]
 
         return color
-    
-    @staticmethod
-    def rgb(color):
-        return [c * 255 for c in Colors.color(color)]
+
+    def rgb(self, color):
+        return [c * 255 for c in self.parse(color)]
+        
+    def is_light(self, color):
+        # tells you if color is dark or light, so you can up or down the scale for improved contrast
+        return colorsys.rgb_to_hls(*self.rgb(color))[1] > 150
+
+    def darker(self, color, step):
+        # returns color darker by step (where step is in range 0..255)
+        hls = colorsys.rgb_to_hls(*self.rgb(color))
+        return colorsys.hls_to_rgb(hls[0], hls[1] - step, hls[2])
+        
 
 class Area(gtk.DrawingArea):
     """Abstraction on top of DrawingArea to work specifically with cairo"""
@@ -55,7 +66,10 @@ class Area(gtk.DrawingArea):
         "expose-event": "override",
         "configure_event": "override",
         "mouse-over": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT, )),
+        "button-press": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT, )),
         "button-release": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT, )),
+        "mouse-move": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT)),
+        "mouse-click": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT)),
     }
 
     def __init__(self):
@@ -66,6 +80,7 @@ class Area(gtk.DrawingArea):
                         | gtk.gdk.BUTTON_RELEASE_MASK
                         | gtk.gdk.POINTER_MOTION_MASK
                         | gtk.gdk.POINTER_MOTION_HINT_MASK)
+        self.connect("button_press_event", self.__on_button_press)
         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)
@@ -76,12 +91,16 @@ class Area(gtk.DrawingArea):
         self.context, self.layout = None, None
         self.width, self.height = None, None
         self.__prev_mouse_regions = None
-        
+
         self.tweener = pytweener.Tweener(0.4, pytweener.Easing.Cubic.easeInOut)
         self.framerate = 80
         self.last_frame_time = None
         self.__animating = False
 
+        self.mouse_drag = (None, None)
+        
+        self.colors = Colors() # handier this way
+
     def on_expose(self):
         """ on_expose event is where you hook in all your drawing
             canvas has been initialized for you """
@@ -93,15 +112,15 @@ class Area(gtk.DrawingArea):
             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)
 
@@ -111,13 +130,15 @@ class Area(gtk.DrawingArea):
         return self.__animating
 
 
-    def animate(self, object, params = {}, duration = None, easing = None, callback = None):
+    def animate(self, object, params = {}, duration = None, easing = None, callback = None, instant = True):
         if duration: params["tweenTime"] = duration  # if none will fallback to tweener default
         if easing: params["tweenType"] = easing    # if none will fallback to tweener default
         if callback: params["onCompleteFunction"] = callback
         self.tweener.addTween(object, **params)
-        self.redraw_canvas()
-    
+
+        if instant:
+            self.redraw_canvas()
+
 
     """ drawing on canvas bits """
     def draw_rect(self, x, y, w, h, corner_radius = 0):
@@ -131,7 +152,7 @@ class Area(gtk.DrawingArea):
         x2, y2 = x + w, y + h
 
         half_corner = corner_radius / 2
-        
+
         self.context.move_to(x + corner_radius, y);
         self.context.line_to(x2 - corner_radius, y);
         # top-right
@@ -162,7 +183,7 @@ class Area(gtk.DrawingArea):
         if color:
             self.set_color(color, opacity)
         self.context.rectangle(x, y, w, h)
-    
+
     def fill_area(self, x, y, w, h, color, opacity = 0):
         self.rectangle(x, y, w, h, color, opacity)
         self.context.fill()
@@ -171,9 +192,9 @@ class Area(gtk.DrawingArea):
         # sets text and returns width and height of the layout
         self.layout.set_text(text)
         return self.layout.get_pixel_size()
-        
+
     def set_color(self, color, opacity = None):
-        color = Colors.color(color) #parse whatever we have there into a normalized triplet
+        color = self.colors.parse(color) #parse whatever we have there into a normalized triplet
 
         if opacity:
             self.context.set_source_rgba(color[0], color[1], color[2], opacity)
@@ -204,28 +225,30 @@ class Area(gtk.DrawingArea):
         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.on_expose()
 
 
     """ mouse events """
     def __on_mouse_move(self, area, event):
-        if not self.mouse_regions:
-            return
-
         if event.is_hint:
             x, y, state = event.window.get_pointer()
         else:
             x = event.x
             y = event.y
             state = event.state
-        
+
+        self.emit("mouse-move", (x, y), state)
+
+        if not self.mouse_regions:
+            return
+
         mouse_regions = []
         for region in self.mouse_regions:
             if region[0] < x < region[2] and region[1] < y < region[3]:
                 mouse_regions.append(region[4])
-        
+
         if mouse_regions:
             area.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2))
         else:
@@ -240,14 +263,40 @@ class Area(gtk.DrawingArea):
         self.__prev_mouse_regions = None
         self.emit("mouse-over", [])
 
-    def __on_button_release(self, area, event):
+
+    def __on_button_press(self, area, event):
+        x = event.x
+        y = event.y
+        state = event.state
+        self.mouse_drag = (x, y)
+
         if not self.mouse_regions:
             return
+        mouse_regions = []
+        for region in self.mouse_regions:
+            if region[0] < x < region[2] and region[1] < y < region[3]:
+                mouse_regions.append(region[4])
+
+        if mouse_regions:
+            self.emit("button-press", mouse_regions)
 
+
+    def __on_button_release(self, area, event):
         x = event.x
         y = event.y
         state = event.state
-        
+
+        click = False
+        drag_distance = 5
+        if self.mouse_drag and (self.mouse_drag[0] - x) ** 2 + (self.mouse_drag[1] - y) ** 2 < drag_distance ** 2:
+            #if the drag is less than the drag distance, then we have a click
+            click =  True
+        self.mouse_drag = None
+
+        if not self.mouse_regions:
+            self.emit("mouse-click", (x,y), [])
+            return
+
         mouse_regions = []
         for region in self.mouse_regions:
             if region[0] < x < region[2] and region[1] < y < region[3]:
@@ -256,7 +305,8 @@ class Area(gtk.DrawingArea):
         if mouse_regions:
             self.emit("button-release", mouse_regions)
 
- 
+        self.emit("mouse-click", (x,y), mouse_regions)
+
 
 
 """ simple example """
@@ -267,31 +317,31 @@ class SampleArea(Area):
         self.rect_width, self.rect_height = 90, 90
 
         self.text_y = -100
-        
-        
+
+
     def on_expose(self):
         # on expose is called when we are ready to draw
-        
+
         # fill_area is just a shortcut function
         # feel free to use self.context. move_to, line_to and others
         self.font_size = 32
         self.layout.set_text("Hello, World!")
-        
+
         self.draw_rect(round(self.rect_x),
                        round(self.rect_y),
                        self.rect_width,
                        self.rect_height,
                        10)
-        
+
         self.set_color("#ff00ff")
         self.context.fill()
 
         self.context.move_to((self.width - self.layout.get_pixel_size()[0]) / 2,
                              self.text_y)
-        
+
         self.set_color("#333")
         self.context.show_layout(self.layout)
-        
+
 
 class BasicWindow:
     def __init__(self):
@@ -299,20 +349,20 @@ class BasicWindow:
         window.set_title("Graphics Module")
         window.set_size_request(300, 300)
         window.connect("delete_event", lambda *args: gtk.main_quit())
-    
+
         self.graphic = SampleArea()
-        
+
         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)
-    
+
         window.add(box)
         window.show_all()
-        
+
         # drop the hello on init
         self.graphic.animate(self.graphic,
                             dict(text_y = 120),
@@ -322,13 +372,13 @@ class BasicWindow:
 
     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
@@ -341,4 +391,3 @@ class BasicWindow:
 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 113b4a5..c8c3876 100644
--- a/src/gui/widgets/pytweener.py
+++ b/src/gui/widgets/pytweener.py
@@ -5,202 +5,150 @@
 # Heavily based on caurina Tweener: http://code.google.com/p/tweener/
 #
 # Released under M.I.T License - see above url
-# Python version by Ben Harling 2009 
+# Python version by Ben Harling 2009
+# All kinds of slashing and dashing by Toms Baugis 2010
 import math
 
-class Tweener:
-    def __init__(self, duration = 0.5, tween = None):
+class Tweener(object):
+    def __init__(self, default_duration = None, tween = None):
         """Tweener
         This class manages all active tweens, and provides a factory for
         creating and spawning tween motions."""
-        self.currentTweens = []
+        self.currentTweens = {}
         self.defaultTweenType = tween or Easing.Cubic.easeInOut
-        self.defaultDuration = duration or 1.0
- 
+        self.defaultDuration = default_duration or 1.0
+
     def hasTweens(self):
         return len(self.currentTweens) > 0
- 
- 
+
+
     def addTween(self, obj, **kwargs):
         """ addTween( object, **kwargs) -> tweenObject or False
- 
+
             Example:
             tweener.addTween( myRocket, throttle=50, setThrust=400, tweenTime=5.0, tweenType=tweener.OUT_QUAD )
- 
+
             You must first specify an object, and at least one property or function with a corresponding
             change value. The tween will throw an error if you specify an attribute the object does
             not possess. Also the data types of the change and the initial value of the tweened item
             must match. If you specify a 'set' -type function, the tweener will attempt to get the
-            starting value by call the corresponding 'get' function on the object. If you specify a 
-            property, the tweener will read the current state as the starting value. You add both 
+            starting value by call the corresponding 'get' function on the object. If you specify a
+            property, the tweener will read the current state as the starting value. You add both
             functions and property changes to the same tween.
- 
+
             in addition to any properties you specify on the object, these keywords do additional
             setup of the tween.
- 
+
             tweenTime = the duration of the motion
             tweenType = one of the predefined tweening equations or your own function
-            onCompleteFunction = specify a function to call on completion of the tween
-            onUpdateFunction = specify a function to call every time the tween updates
+            onComplete = specify a function to call on completion of the tween
+            onUpdate = specify a function to call every time the tween updates
             tweenDelay = specify a delay before starting.
             """
         if "tweenTime" in kwargs:
             t_time = kwargs.pop("tweenTime")
         else: t_time = self.defaultDuration
- 
+
         if "tweenType" in kwargs:
             t_type = kwargs.pop("tweenType")
         else: t_type = self.defaultTweenType
- 
-        if "onCompleteFunction" in kwargs:
-            t_completeFunc = kwargs.pop("onCompleteFunction")
+
+        if "onComplete" in kwargs:
+            t_completeFunc = kwargs.pop("onComplete")
         else: t_completeFunc = None
- 
-        if "onUpdateFunction" in kwargs:
-            t_updateFunc = kwargs.pop("onUpdateFunction")
+
+        if "onUpdate" in kwargs:
+            t_updateFunc = kwargs.pop("onUpdate")
         else: t_updateFunc = None
- 
+
         if "tweenDelay" in kwargs:
             t_delay = kwargs.pop("tweenDelay")
         else: t_delay = 0
- 
+
         tw = Tween( obj, t_time, t_type, t_completeFunc, t_updateFunc, t_delay, **kwargs )
-        if tw:    
-            self.currentTweens.append( tw )
+        if tw:
+            tweenlist = self.currentTweens.setdefault(obj, [])
+            tweenlist.append(tw)
         return tw
- 
+
     def removeTween(self, tweenObj):
-        if tweenObj in self.currentTweens:
-            tweenObj.complete = True
-            #self.currentTweens.remove( tweenObj )
- 
+        tweenObj.complete = True
+
     def getTweensAffectingObject(self, obj):
         """Get a list of all tweens acting on the specified object
         Useful for manipulating tweens on the fly"""
-        tweens = []
-        for t in self.currentTweens:
-            if t.target is obj:
-                tweens.append(t)
-        return tweens
- 
-    def removeTweeningFrom(self, obj):
+        return self.currentTweens.get(obj, [])
+
+    def killTweensOf(self, obj):
         """Stop tweening an object, without completing the motion
         or firing the completeFunction"""
-        for t in self.currentTweens:
-            if t.target is obj:
-                t.complete = True
- 
+        try:
+            del self.currentTweens[obj]
+        except:
+            pass
+
+
     def finish(self):
         #go to last frame for all tweens
-        for t in self.currentTweens:
-            t.update(t.duration)
-        self.currentTweens = []
- 
+        for obj in self.currentTweens:
+            for t in self.currentTweens[obj]:
+                t.update(t.duration)
+        self.currentTweens = {}
+
     def update(self, timeSinceLastFrame):
-        removable = []
-        for t in self.currentTweens:
-            t.update(timeSinceLastFrame)
-
-            if t.complete:
-                removable.append(t)
-                
-        for t in removable:
-            self.currentTweens.remove(t)
-            
- 
+        for obj in self.currentTweens.keys():
+            # updating tweens from last to first and deleting while at it
+            # in order to not confuse the index
+            for i, t in reversed(list(enumerate(self.currentTweens[obj]))):
+                t.update(timeSinceLastFrame)
+                if t.complete:
+                    del self.currentTweens[obj][i]
+
+                if not self.currentTweens[obj]:
+                    del self.currentTweens[obj]
+
 class Tween(object):
-    def __init__(self, obj, tduration, tweenType, completeFunction, updateFunction, delay, **kwargs):
-        """Tween object:
-            Can be created directly, but much more easily using Tweener.addTween( ... )
-            """
-        #print obj, tduration, kwargs
-        self.duration = tduration
+    __slots__ = ['duration', 'delay', 'target', 'tween', 'tweenables', 'delta',
+                 'target', 'ease', 'tweenables', 'delta', 'completeFunction',
+                 'updateFunction', 'complete', 'paused']
+
+    def __init__(self, obj, duration, easing, on_complete, on_update, delay, **kwargs):
+        """Tween object use Tweener.addTween( ... ) to create"""
+        self.duration = duration
         self.delay = delay
         self.target = obj
-        self.tween = tweenType
-        self.tweenables = kwargs
+        self.ease = easing
+
+        # list of (property, start_value, end_value)
+        self.tweenables = [(k, self.target.__dict__[k], v) for k, v in kwargs.items()]
+
         self.delta = 0
-        self.completeFunction = completeFunction
-        self.updateFunction = updateFunction
+        self.completeFunction = on_complete
+        self.updateFunction = on_update
         self.complete = False
-        self.tProps = []
-        self.tFuncs = []
+
         self.paused = self.delay > 0
-        self.decodeArguments()
- 
-    def decodeArguments(self):
-        """Internal setup procedure to create tweenables and work out
-           how to deal with each"""
- 
-        if len(self.tweenables) == 0:
-            # nothing to do 
-            print "TWEEN ERROR: No Tweenable properties or functions defined"
-            self.complete = True
-            return
- 
-        for k, v in self.tweenables.items():
- 
-        # check that its compatible
-            if not hasattr( self.target, k):
-                print "TWEEN ERROR: " + str(self.target) + " has no function " + k
-                self.complete = True
-                break
- 
-            prop = func = False
-            startVal = 0
-            newVal = v
- 
-            try:
-                startVal = self.target.__dict__[k]
-                prop = k
-                propName = k
- 
-            except:
-                func = getattr( self.target, k)
-                funcName = k
- 
-            if func:
-                try:
-                    getFunc = getattr(self.target, funcName.replace("set", "get") )
-                    startVal = getFunc()
-                except:
-                    # no start value, assume its 0
-                    # but make sure the start and change
-                    # dataTypes match :)
-                    startVal = newVal * 0
-                tweenable = Tweenable( startVal, newVal - startVal)    
-                newFunc = [ k, func, tweenable]
- 
-                #setattr(self, funcName, newFunc[2])
-                self.tFuncs.append( newFunc )
- 
- 
-            if prop:
-                tweenable = Tweenable( startVal, newVal - startVal)    
-                newProp = [ k, prop, tweenable]
-                self.tProps.append( newProp )  
- 
- 
+
     def pause( self, numSeconds=-1 ):
         """Pause this tween
             do tween.pause( 2 ) to pause for a specific time
             or tween.pause() which pauses indefinitely."""
         self.paused = True
         self.delay = numSeconds
- 
+
     def resume( self ):
         """Resume from pause"""
         if self.paused:
             self.paused=False
- 
+
     def update(self, ptime):
-        """Update this tween with the time since the last frame
-            if there is an update function, it is always called
-            whether the tween is running or paused"""
-            
+        """Update tween with the time since the last frame
+           if there is an update callback, it is always called
+           whether the tween is running or paused"""
+
         if self.complete:
             return
-        
+
         if self.paused:
             if self.delay > 0:
                 self.delay = max( 0, self.delay - ptime )
@@ -210,65 +158,22 @@ class Tween(object):
                 if self.updateFunction:
                     self.updateFunction()
             return
- 
-        self.delta = min(self.delta + ptime, self.duration)
- 
-
-        for propName, prop, tweenable in self.tProps:
-            self.target.__dict__[prop] = self.tween( self.delta, tweenable.startValue, tweenable.change, self.duration )
-        for funcName, func, tweenable in self.tFuncs:
-            func( self.tween( self.delta, tweenable.startValue, tweenable.change, self.duration ) )
- 
- 
+
+        self.delta = self.delta + ptime
+        if self.delta > self.duration:
+            self.delta = self.duration
+
+
+        for prop, start_value, end_value in self.tweenables:
+            self.target.__dict__[prop] = self.ease(self.delta, start_value, end_value - start_value, self.duration)
+
         if self.delta == self.duration:
             self.complete = True
             if self.completeFunction:
                 self.completeFunction()
- 
+
         if self.updateFunction:
             self.updateFunction()
- 
- 
- 
-    def getTweenable(self, name):
-        """Return the tweenable values corresponding to the name of the original
-        tweening function or property. 
- 
-        Allows the parameters of tweens to be changed at runtime. The parameters
-        can even be tweened themselves!
- 
-        eg:
- 
-        # the rocket needs to escape!! - we're already moving, but must go faster!
-        twn = tweener.getTweensAffectingObject( myRocket )[0]
-        tweenable = twn.getTweenable( "thrusterPower" )
-        tweener.addTween( tweenable, change=1000.0, tweenTime=0.4, tweenType=tweener.IN_QUAD )
- 
-        """
-        ret = None
-        for n, f, t in self.tFuncs:
-            if n == name:
-                ret = t
-                return ret
-        for n, p, t in self.tProps:
-            if n == name:
-                ret = t
-                return ret
-        return ret
- 
-    def Remove(self):
-        """Disables and removes this tween
-            without calling the complete function"""
-        self.complete = True
-
- 
-class Tweenable:
-    def __init__(self, start, change):
-        """Tweenable:
-            Holds values for anything that can be tweened
-            these are normally only created by Tweens"""
-        self.startValue = start
-        self.change = change
 
 
 """Robert Penner's easing classes ported over from actionscript by Toms Baugis (at gmail com).
@@ -276,7 +181,7 @@ There certainly is room for improvement, but wanted to keep the readability to s
 
 ================================================================================
  Easing Equations
- (c) 2003 Robert Penner, all rights reserved. 
+ (c) 2003 Robert Penner, all rights reserved.
  This work is subject to the terms in
  http://www.robertpenner.com/easing_terms_of_use.html.
 ================================================================================
@@ -310,44 +215,44 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 """
-class Easing:
-    class Back:
+class Easing(object):
+    class Back(object):
         @staticmethod
         def easeIn(t, b, c, d, s = 1.70158):
             t = t / d
-            return c * t**2 * ((s+1) * t - s) + b
+            return c * t * t * ((s+1) * t - s) + b
 
         @staticmethod
         def easeOut (t, b, c, d, s = 1.70158):
             t = t / d - 1
-            return c * (t**2 * ((s + 1) * t + s) + 1) + b
+            return c * (t * t * ((s + 1) * t + s) + 1) + b
 
         @staticmethod
         def easeInOut (t, b, c, d, s = 1.70158):
             t = t / (d * 0.5)
             s = s * 1.525
-            
+
             if t < 1:
-                return c * 0.5 * (t**2 * ((s + 1) * t - s)) + b
+                return c * 0.5 * (t * t * ((s + 1) * t - s)) + b
 
             t = t - 2
-            return c / 2 * (t**2 * ((s + 1) * t + s) + 2) + b
+            return c / 2 * (t * t * ((s + 1) * t + s) + 2) + b
 
-    class Bounce:
+    class Bounce(object):
         @staticmethod
         def easeOut (t, b, c, d):
             t = t / d
             if t < 1 / 2.75:
-                return c * (7.5625 * t**2) + b
+                return c * (7.5625 * t * t) + b
             elif t < 2 / 2.75:
                 t = t - 1.5 / 2.75
-                return c * (7.5625 * t**2 + 0.75) + b
+                return c * (7.5625 * t * t + 0.75) + b
             elif t < 2.5 / 2.75:
                 t = t - 2.25 / 2.75
-                return c * (7.5625 * t**2 + .9375) + b
+                return c * (7.5625 * t * t + .9375) + b
             else:
                 t = t - 2.625 / 2.75
-                return c * (7.5625 * t**2 + 0.984375) + b
+                return c * (7.5625 * t * t + 0.984375) + b
 
         @staticmethod
         def easeIn (t, b, c, d):
@@ -361,57 +266,57 @@ class Easing:
             return Easing.Bounce.easeOut (t * 2 -d, 0, c, d) * .5 + c*.5 + b
 
 
-        
-    class Circ:
+
+    class Circ(object):
         @staticmethod
         def easeIn (t, b, c, d):
             t = t / d
-            return -c * (math.sqrt(1 - t**2) - 1) + b
+            return -c * (math.sqrt(1 - t * t) - 1) + b
 
         @staticmethod
         def easeOut (t, b, c, d):
             t = t / d - 1
-            return c * math.sqrt(1 - t**2) + b
+            return c * math.sqrt(1 - t * t) + b
 
         @staticmethod
         def easeInOut (t, b, c, d):
             t = t / (d * 0.5)
             if t < 1:
-                return -c * 0.5 * (math.sqrt(1 - t**2) - 1) + b
-            
+                return -c * 0.5 * (math.sqrt(1 - t * t) - 1) + b
+
             t = t - 2
-            return c*0.5 * (math.sqrt(1 - t**2) + 1) + b
+            return c*0.5 * (math.sqrt(1 - t * t) + 1) + b
 
 
-    class Cubic:
+    class Cubic(object):
         @staticmethod
         def easeIn (t, b, c, d):
             t = t / d
-            return c * t**3 + b
+            return c * t * t * t + b
 
         @staticmethod
         def easeOut (t, b, c, d):
             t = t / d - 1
-            return c * (t**3 + 1) + b
+            return c * (t * t * t + 1) + b
 
         @staticmethod
         def easeInOut (t, b, c, d):
             t = t / (d * 0.5)
             if t < 1:
-                return c * 0.5 * t**3 + b
-            
+                return c * 0.5 * t * t * t + b
+
             t = t - 2
-            return c * 0.5 * (t**3 + 2) + b
+            return c * 0.5 * (t * t * t + 2) + b
 
 
-    class Elastic:
+    class Elastic(object):
         @staticmethod
         def easeIn (t, b, c, d, a = 0, p = 0):
             if t==0: return b
 
-            t = t / d            
+            t = t / d
             if t == 1: return b+c
-            
+
             if not p: p = d * .3;
 
             if not a or a < abs(c):
@@ -419,18 +324,18 @@ class Easing:
                 s = p / 4
             else:
                 s = p / (2 * math.pi) * math.asin(c / a)
-            
-            t = t - 1            
+
+            t = t - 1
             return - (a * math.pow(2, 10 * t) * math.sin((t*d-s) * (2 * math.pi) / p)) + b
 
 
         @staticmethod
         def easeOut (t, b, c, d, a = 0, p = 0):
             if t == 0: return b
-            
+
             t = t / d
             if (t == 1): return b + c
-            
+
             if not p: p = d * .3;
 
             if not a or a < abs(c):
@@ -438,17 +343,17 @@ class Easing:
                 s = p / 4
             else:
                 s = p / (2 * math.pi) * math.asin(c / a)
-                
+
             return a * math.pow(2,-10 * t) * math.sin((t * d - s) * (2 * math.pi) / p) + c + b
 
 
         @staticmethod
         def easeInOut (t, b, c, d, a = 0, p = 0):
             if t == 0: return b
-            
+
             t = t / (d * 0.5)
             if t == 2: return b + c
-            
+
             if not p: p = d * (.3 * 1.5)
 
             if not a or a < abs(c):
@@ -456,16 +361,16 @@ class Easing:
                 s = p / 4
             else:
                 s = p / (2 * math.pi) * math.asin(c / a)
-                
+
             if (t < 1):
                 t = t - 1
                 return -.5 * (a * math.pow(2, 10 * t) * math.sin((t * d - s) * (2 * math.pi) / p)) + b
-                
+
             t = t - 1
             return a * math.pow(2, -10 * t) * math.sin((t * d - s) * (2 * math.pi) / p) * .5 + c + b
 
 
-    class Expo:
+    class Expo(object):
         @staticmethod
         def easeIn(t, b, c, d):
             if t == 0:
@@ -488,14 +393,14 @@ class Easing:
                 return b+c
 
             t = t / (d * 0.5)
-            
+
             if t < 1:
                 return c * 0.5 * math.pow(2, 10 * (t - 1)) + b
-            
+
             return c * 0.5 * (-math.pow(2, -10 * (t - 1)) + 2) + b
 
 
-    class Linear:
+    class Linear(object):
         @staticmethod
         def easeNone(t, b, c, d):
             return c * t / d + b
@@ -513,11 +418,11 @@ class Easing:
             return c * t / d + b
 
 
-    class Quad:
+    class Quad(object):
         @staticmethod
         def easeIn (t, b, c, d):
             t = t / d
-            return c * t**2 + b
+            return c * t * t + b
 
         @staticmethod
         def easeOut (t, b, c, d):
@@ -528,54 +433,54 @@ class Easing:
         def easeInOut (t, b, c, d):
             t = t / (d * 0.5)
             if t < 1:
-                return c * 0.5 * t**2 + b
-            
+                return c * 0.5 * t * t + b
+
             t = t - 1
             return -c * 0.5 * (t * (t - 2) - 1) + b
 
 
-    class Quart:
+    class Quart(object):
         @staticmethod
         def easeIn (t, b, c, d):
             t = t / d
-            return c * t**4 + b
+            return c * t * t * t * t + b
 
         @staticmethod
         def easeOut (t, b, c, d):
             t = t / d - 1
-            return -c * (t**4 - 1) + b
+            return -c * (t * t * t * t - 1) + b
 
         @staticmethod
         def easeInOut (t, b, c, d):
             t = t / (d * 0.5)
             if t < 1:
-                return c * 0.5 * t**4 + b
-            
+                return c * 0.5 * t * t * t * t + b
+
             t = t - 2
-            return -c * 0.5 * (t**4 - 2) + b
+            return -c * 0.5 * (t * t * t * t - 2) + b
+
 
-    
-    class Quint:
+    class Quint(object):
         @staticmethod
         def easeIn (t, b, c, d):
             t = t / d
-            return c * t**5 + b
+            return c * t * t * t * t * t + b
 
         @staticmethod
         def easeOut (t, b, c, d):
             t = t / d - 1
-            return c * (t**5 + 1) + b
+            return c * (t * t * t * t * t + 1) + b
 
         @staticmethod
         def easeInOut (t, b, c, d):
             t = t / (d * 0.5)
             if t < 1:
-                return c * 0.5 * t**5 + b
-            
+                return c * 0.5 * t * t * t * t * t + b
+
             t = t - 2
-            return c * 0.5 * (t**5 + 2) + b
+            return c * 0.5 * (t * t * t * t * t + 2) + b
 
-    class Sine:
+    class Sine(object):
         @staticmethod
         def easeIn (t, b, c, d):
             return -c * math.cos(t / d * (math.pi / 2)) + c + b
@@ -589,7 +494,7 @@ class Easing:
             return -c * 0.5 * (math.cos(math.pi * t / d) - 1) + b
 
 
-    class Strong:
+    class Strong(object):
         @staticmethod
         def easeIn(t, b, c, d):
             return c * (t/d)**5 + b
@@ -601,55 +506,52 @@ class Easing:
         @staticmethod
         def easeInOut(t, b, c, d):
             t = t / (d * 0.5)
-            
+
             if t < 1:
-                return c * 0.5 * t**5 + b
-            
+                return c * 0.5 * t * t * t * t * t + b
+
             t = t - 2
-            return c * 0.5 * (t**5 + 2) + b
-
-
-
-class TweenTestObject:
-    def __init__(self):
-        self.pos = 20
-        self.rot = 50
- 
-    def update(self):
-        print self.pos, self.rot
- 
-    def setRotation(self, rot):
-        self.rot = rot
- 
-    def getRotation(self):
-        return self.rot
- 
-    def complete(self):
-        print "I'm done tweening now mommy!"
- 
- 
-if __name__=="__main__":
-    import time
-    T = Tweener()
-    tst = TweenTestObject()
-    mt = T.addTween( tst, setRotation=500.0, tweenTime=2.5, tweenType=T.OUT_EXPO, 
-                      pos=-200, tweenDelay=0.4, onCompleteFunction=tst.complete, 
-                      onUpdateFunction=tst.update )
-    s = time.clock()
-    changed = False
-    while T.hasTweens():
-        tm = time.clock()
-        d = tm - s
-        s = tm
-        T.update( d )
-        if mt.delta > 1.0 and not changed:
- 
-            tweenable = mt.getTweenable( "setRotation" )
- 
-            T.addTween( tweenable, change=-1000, tweenTime=0.7 )
-            T.addTween( mt, duration=-0.2, tweenTime=0.2 )
-            changed = True
-        #print mt.duration,
-        print tst.getRotation(), tst.pos
-        time.sleep(0.06)
-    print tst.getRotation(), tst.pos
+            return c * 0.5 * (t * t * t * t * t + 2) + b
+
+
+
+
+class _PerformanceTester(object):
+    def __init__(self, a, b, c):
+        self.a = a
+        self.b = b
+        self.c = c
+
+if __name__ == "__main__":
+    import datetime as dt
+
+    tweener = Tweener()
+    objects = []
+    for i in range(10000):
+        objects.append(_PerformanceTester(i-100, i-100, i-100))
+
+
+    total = dt.datetime.now()
+
+    t = dt.datetime.now()
+    for i, o in enumerate(objects):
+        tweener.addTween(o, a = i, b = i, c = i, tweenTime = 1.0)
+    print "add", dt.datetime.now() - t
+
+    t = dt.datetime.now()
+
+    for i in range(10):
+        tweener.update(0.01)
+    print "update", dt.datetime.now() - t
+
+    t = dt.datetime.now()
+
+    for i in range(10):
+        for i, o in enumerate(objects):
+            tweener.killTweensOf(o)
+            tweener.addTween(o, a = i, b = i, c = i, tweenTime = 1.0)
+    print "kill-add", dt.datetime.now() - t
+
+    print "total", dt.datetime.now() - total
+
+



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