[hamster-applet] sync with experiments. most notably tweener now calls setattr instead of poking values directly and



commit 033006378a6f640c1fe0c5940ba3147c284d4c0c
Author: Toms Bauģis <toms baugis gmail com>
Date:   Sun Jun 13 14:31:32 2010 +0100

    sync with experiments. most notably tweener now calls setattr instead of poking values directly and sprite in setter checks if value has changed before marking sprite as dirty (and thus queing redraw)

 src/hamster/graphics.py  |   65 +++++++++++++++++---------
 src/hamster/pytweener.py |  115 +++++++++++++++++++++++++++++++++++++--------
 2 files changed, 136 insertions(+), 44 deletions(-)
---
diff --git a/src/hamster/graphics.py b/src/hamster/graphics.py
index de24254..690f641 100644
--- a/src/hamster/graphics.py
+++ b/src/hamster/graphics.py
@@ -483,12 +483,13 @@ class Sprite(gtk.Object):
         #: drawing order between siblings. The one with the highest z_order will be on top.
         self.z_order = z_order
 
-        self._sprite_dirty = True # flag that indicates that the graphics object of the sprite should be rendered
+        self.__dict__["_sprite_dirty"] = True # flag that indicates that the graphics object of the sprite should be rendered
 
     def __setattr__(self, name, val):
-        self.__dict__[name] = val
-        if name not in ('_sprite_dirty', 'x', 'y', 'rotation', 'scale_x', 'scale_y'):
-            self.__dict__["_sprite_dirty"] = True
+        if self.__dict__.get(name, "hamster_graphics_no_value_really") != val:
+            self.__dict__[name] = val
+            if name not in ('x', 'y', 'rotation', 'scale_x', 'scale_y'):
+                self.__dict__["_sprite_dirty"] = True
 
 
     def add_child(self, *sprites):
@@ -526,7 +527,7 @@ class Sprite(gtk.Object):
 
         if (self._sprite_dirty): # send signal to redo the drawing when sprite's dirty
             self.emit("on-render")
-            self._sprite_dirty = False
+            self.__dict__["_sprite_dirty"] = False
 
         self.graphics._draw(context, self.interactive or self.draggable)
 
@@ -591,8 +592,6 @@ class Label(Sprite):
 
     def __setattr__(self, name, val):
         Sprite.__setattr__(self, name, val)
-        if name == "_sprite_dirty":
-            return
 
         if name == "width":
             # setting width means consumer wants to contrain the label
@@ -798,6 +797,14 @@ class Scene(gtk.DrawingArea):
         #: read only info about current framerate (frames per second)
         self.fps = 0 # inner frames per second counter
 
+        #: Mouse cursor appearance.
+        #: Replace with your own cursor or set to False to have no cursor.
+        #: None will revert back the default behavior
+        self.mouse_cursor = None
+
+        blank_pixmap = gtk.gdk.Pixmap(None, 1, 1, 1)
+        self._blank_cursor = gtk.gdk.Cursor(blank_pixmap, blank_pixmap, gtk.gdk.Color(), gtk.gdk.Color(), 0, 0)
+
 
         self._last_frame_time = None
         self._mouse_sprites = set()
@@ -805,6 +812,7 @@ class Scene(gtk.DrawingArea):
         self._drag_sprite = None
         self._button_press_time = None # to distinguish between click and drag
 
+
         self._mouse_in = False
 
         self.__drawing_queued = False
@@ -857,14 +865,15 @@ class Scene(gtk.DrawingArea):
         if not self.tweener: # here we complain
             raise Exception("pytweener was not found. Include it to enable animations")
 
-        self.tweener.add_tween(sprite,
-                               duration=duration,
-                               easing=easing,
-                               on_complete=on_complete,
-                               on_update=on_update,
-                               delay=delay, **kwargs)
-
+        tween = self.tweener.add_tween(sprite,
+                                       duration=duration,
+                                       easing=easing,
+                                       on_complete=on_complete,
+                                       on_update=on_update,
+                                       delay=delay, **kwargs)
         self.redraw()
+        return tween
+
 
     # exposure events
     def do_configure_event(self, event):
@@ -915,8 +924,6 @@ class Scene(gtk.DrawingArea):
 
         if self._drag_sprite and self._drag_sprite.draggable \
            and gtk.gdk.BUTTON1_MASK & event.state:
-            self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.FLEUR))
-
             # dragging around
             drag = self._mouse_drag \
                    and (self._mouse_drag[0] - event.x) ** 2 + \
@@ -959,17 +966,25 @@ class Scene(gtk.DrawingArea):
         if mouse_x is None or self._mouse_in == False:
             return
 
-        #check if we have a mouse over
-        over = set()
+        custom_mouse = self.mouse_cursor is not None
 
         cursor = gtk.gdk.ARROW
+        if custom_mouse:
+            if self.mouse_cursor == False:
+                cursor = self._blank_cursor
+            else:
+                cursor = self.mouse_cursor
 
+
+        #check if we have a mouse over
+        over = set()
         for sprite in self.all_sprites():
             if sprite.interactive and self._check_hit(sprite, mouse_x, mouse_y):
-                if sprite.draggable:
-                    cursor = gtk.gdk.FLEUR
-                else:
-                    cursor = gtk.gdk.HAND2
+                if custom_mouse == False:
+                    if sprite.draggable:
+                        cursor = gtk.gdk.FLEUR
+                    else:
+                        cursor = gtk.gdk.HAND2
 
                 over.add(sprite)
 
@@ -992,7 +1007,11 @@ class Scene(gtk.DrawingArea):
 
 
         self._mouse_sprites = over
-        self.window.set_cursor(gtk.gdk.Cursor(cursor))
+
+        if isinstance(cursor, gtk.gdk.Cursor):
+            self.window.set_cursor(cursor)
+        else:
+            self.window.set_cursor(gtk.gdk.Cursor(cursor))
 
 
     def __on_mouse_enter(self, area, event):
diff --git a/src/hamster/pytweener.py b/src/hamster/pytweener.py
index 64b1235..6c93340 100644
--- a/src/hamster/pytweener.py
+++ b/src/hamster/pytweener.py
@@ -8,7 +8,10 @@
 # Python version by Ben Harling 2009
 # All kinds of slashing and dashing by Toms Baugis 2010
 import math
-import collections, itertools
+import collections
+import datetime as dt
+import time
+import re
 
 class Tweener(object):
     def __init__(self, default_duration = None, tween = None):
@@ -30,13 +33,26 @@ class Tweener(object):
             This will move the sprite to coordinates (500, 200) in 0.4 seconds.
             For parameter "easing" you can use one of the pytweener.Easing
             functions, or specify your own.
+            The tweener can handle numbers, dates and color strings in hex ("#ffffff")
         """
         duration = duration or self.default_duration
         easing = easing or self.default_easing
         delay = delay or 0
 
         tw = Tween(obj, duration, easing, on_complete, on_update, delay, **kwargs )
+
+        if obj in self.current_tweens:
+            for current_tween in self.current_tweens[obj]:
+                prev_keys = set((tweenable.key for tweenable in current_tween.tweenables))
+                dif = prev_keys & set(kwargs.keys())
+
+                removable = [tweenable for tweenable in current_tween.tweenables if tweenable.key in dif]
+                for tweenable in removable:
+                    current_tween.tweenables.remove(tweenable)
+
+
         self.current_tweens[obj].add(tw)
+        return tw
 
 
     def get_tweens(self, obj):
@@ -55,6 +71,11 @@ class Tweener(object):
         else:
             self.current_tweens = collections.defaultdict(set)
 
+    def remove_tween(self, tween):
+        """"remove given tween without completing the motion or firing the on_complete"""
+        if tween.target in self.current_tweens and tween in self.current_tweens[tween.target]:
+            self.current_tweens[tween.target].remove(tween)
+
     def finish(self):
         """jump the the last frame of all tweens"""
         for obj in self.current_tweens:
@@ -82,6 +103,68 @@ class Tweener(object):
                 del self.current_tweens[tween.target]
 
 
+class Tweenable(object):
+    hex_color_normal = re.compile("#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})")
+    hex_color_short = re.compile("#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])")
+
+    def __init__(self, key, start_value, target_value):
+        self.key = key
+        self.change = None
+        self.decode_func = lambda x: x
+        self.encode_func = lambda x: x
+        self.start_value = start_value
+        self.target_value = target_value
+
+        if isinstance(start_value, int) or isinstance(start_value, float):
+            self.start_value = start_value
+            self.change = target_value - start_value
+        else:
+            if isinstance(start_value, dt.datetime) or isinstance(start_value, dt.date):
+                self.decode_func = lambda x: time.mktime(x.timetuple())
+                if isinstance(start_value, dt.datetime):
+                    self.encode_func = lambda x: dt.datetime.fromtimestamp(x)
+                else:
+                    self.encode_func = lambda x: dt.date.fromtimestamp(x)
+
+                self.start_value = self.decode_func(start_value)
+                self.change = self.decode_func(target_value) - self.start_value
+
+            elif isinstance(start_value, basestring) \
+             and (self.hex_color_normal.match(start_value) or self.hex_color_short.match(start_value)):
+                # code below is mainly based on jquery-color plugin
+                self.encode_func = lambda val: "#%02x%02x%02x" % (max(min(val[0], 255), 0),
+                                                                  max(min(val[1], 255), 0),
+                                                                  max(min(val[2], 255), 0))
+                if self.hex_color_normal.match(start_value):
+                    self.decode_func = lambda val: [int(match, 16)
+                                                    for match in self.hex_color_normal.match(val).groups()]
+
+                elif self.hex_color_short.match(start_value):
+                    self.decode_func = lambda val: [int(match + match, 16)
+                                                    for match in self.hex_color_short.match(val).groups()]
+
+                if self.hex_color_normal.match(target_value):
+                    target_value = [int(match, 16)
+                                    for match in self.hex_color_normal.match(target_value).groups()]
+                else:
+                    target_value = [int(match + match, 16)
+                                    for match in self.hex_color_short.match(target_value).groups()]
+
+                self.start_value = self.decode_func(start_value)
+                self.change = [target - start for start, target in zip(self.start_value, target_value)]
+
+
+    def update(self, ease, delta, duration):
+        # list means we are dealing with a color triplet
+        if isinstance(self.start_value, list):
+            return self.encode_func([ease(delta, self.start_value[i],
+                                                 self.change[i], duration)
+                                                             for i in range(3)])
+        else:
+            return self.encode_func(ease(delta, self.start_value, self.change, duration))
+
+
+
 class Tween(object):
     __slots__ = ('tweenables', 'target', 'delta', 'duration', 'delay',
                  'ease', 'delta', 'on_complete',
@@ -95,7 +178,9 @@ class Tween(object):
         self.ease = easing
 
         # list of (property, start_value, delta)
-        self.tweenables = set(((key, self.target.__dict__[key], value - self.target.__dict__[key]) for key, value in kwargs.items()))
+        self.tweenables = set()
+        for key, value in kwargs.items():
+            self.tweenables.add(Tweenable(key, self.target.__dict__[key], value))
 
         self.delta = 0
         self.on_complete = on_complete
@@ -137,8 +222,9 @@ class Tween(object):
         if self.delta > self.duration:
             self.delta = self.duration
 
-        for prop, start_value, delta_value in self.tweenables:
-            self.target.__dict__[prop] = self.ease(self.delta, start_value, delta_value, self.duration)
+        for tweenable in self.tweenables:
+            self.target.__setattr__(tweenable.key,
+                                    tweenable.update(self.ease, self.delta, self.duration))
 
         if self.delta == self.duration:
             self.complete = True
@@ -505,28 +591,15 @@ if __name__ == "__main__":
     tweener = Tweener()
     objects = []
     for i in range(10000):
-        objects.append(_PerformanceTester(i-100, i-100, i-100))
+        objects.append(_PerformanceTester(dt.datetime.now(), i-100, i-100))
 
 
     total = dt.datetime.now()
 
     t = dt.datetime.now()
     for i, o in enumerate(objects):
-        tweener.add_tween(o, a = i, b = i, c = i, duration = 1.0)
+        tweener.add_tween(o, a = dt.datetime.now() - dt.timedelta(days=3), b = i, c = i, duration = 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.kill_tweens(o)
-            tweener.add_tween(o, a = i, b = i, c = i, duration = 1.0)
-    print "kill-add", dt.datetime.now() - t
-
-    print "total", dt.datetime.now() - total
+    tweener.finish()
+    print objects[0].a



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