Faster icon rotation using various cachings
authorStefanos Harhalakis <v13@v13.gr>
Tue, 3 Aug 2010 18:45:36 +0000 (18:45 +0000)
committerStefanos Harhalakis <v13@v13.gr>
Tue, 3 Aug 2010 18:45:36 +0000 (18:45 +0000)
src/icon.py
src/icongrid.py

index df559cc..aa8d5a8 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 # coding=UTF-8
-# 
+#
 # Copyright (C) 2010 Stefanos Harhalakis
 #
 # This file is part of wifieye.
@@ -39,6 +39,9 @@ from xdg.IconTheme import getIconPath
 #import config
 import apps
 
+# Background surface for icons
+iconbg=None
+
 # Load an icon
 # Fall-back to default/blue if not found or name==None
 def getIcon(name, iconsize):
@@ -70,6 +73,7 @@ class Icon(gobject.GObject):
 
        self.name=None
        self.icon=None
+        self.sicon=None
        self.lastpress=0
        self.ispressed=False
 
@@ -84,6 +88,8 @@ class Icon(gobject.GObject):
 
        self.angle=0
 
+       self.cached_icons={}
+
     def timePressed(self):
        """ return how much time a button is pressed """
        dt=time.time() - self.lastpress
@@ -94,48 +100,66 @@ class Icon(gobject.GObject):
        if dt==None:
            self.name=None
            self.icon=None
+           self.sicon=None
        else:
            self.name=dt['id']
            self.icon=dt['icon2']
+           self.sicon=None
+           self.cached_icons={}
        self.invalidate()
 
     def getSize(self):
        return(self.config.iconsize+self.config.iconspace)
 
     def setAngle(self, angle):
-       if self.angle==angle:
-           print "Same angle"
-           return
+       """ Set the angle. Return True if the angle changed or False if it
+       didn't. The caller should invalidate the icon """
+
+       # The step in degrees
+       step=15
+
+       angle2=int(angle/step)*step
 
-       self.angle=angle
+       if angle2==self.angle:
+           return(False)
+
+       self.angle=angle2
 
        # The caller should be responsible for redrawing.
        # If we call invalidate() here there is the risk of having
        # icons rotate individually using different angles
 #      self.invalidate()
 
-    def draw(self, cr, x, y):
-       self.draw_queued=False
-       self.x=x
-       self.y=y
+       return(True)
 
-       if self.icon==None and not self.isconfig:
-           return
+    def mkbg(self, t_pressed):
+       """ Create the background of the icon and cache it as a global
+       variable among all icons and widget instances """
+        global iconbg
 
-       cr.save()
+        if iconbg!=None and t_pressed<=0.001:
+            return(iconbg)
+
+        w=self.config.iconsize + self.config.iconspace
+       s=cairo.ImageSurface(cairo.FORMAT_ARGB32, w, w)
+       cr0=cairo.Context(s)
+       cr=gtk.gdk.CairoContext(cr0)
 
-       cr.set_source_rgba(0.1, 0.1, 0.1, 1)
+       cr.set_source_rgba(0.1, 0.1, 0.1, 1)
        cr.set_line_width(5)
 
        #if self.ispressed:
-       if self.timePressed() <= self.presstime or self.ispressed:
-           t=1.0 * min(self.timePressed(), self.presstime) / self.presstime
+       if t_pressed>0.001 and \
+            (t_pressed <= self.presstime or self.ispressed):
+           t=1.0 * min(t_pressed, self.presstime) / self.presstime
            g=0.3+0.5*t
            b=0.3+0.7*t
            cr.set_source_rgba(0, g, b, 0.7)
        else:
            cr.set_source_rgba(0.3, 0.3, 0.3, 0.7)
 
+        x=0
+        y=0
        x3=x + (self.config.iconspace/6)
        y3=y + (self.config.iconspace/6)
 
@@ -152,62 +176,105 @@ class Icon(gobject.GObject):
        cr.fill()
        cr.clip()
        cr.paint()
-       cr.restore()
+#      cr.restore()
 
-       if self.icon==None:
-           return
+        if t_pressed<0.001:
+            iconbg=s
 
-       icon=self.icon
+        return(s)
 
-       icon2=icon
+    def get_sicon(self):
+       """ Return the icon as a surface. Cache it. """
+       if self.sicon!=None:
+           return(self.sicon)
 
-# Old method. Faster rotation but without support for rotation
-# animation
+        w=self.config.iconsize
+       s=cairo.ImageSurface(cairo.FORMAT_ARGB32, w, w)
+       cr0=cairo.Context(s)
+       cr=gtk.gdk.CairoContext(cr0)
 
-#      if mode=='l':
-#          icon2=icon
-#      else:
-#          icon2=icon.rotate_simple(gdk.PIXBUF_ROTATE_COUNTERCLOCKWISE)
+       cr.set_source_pixbuf(self.icon, 0, 0)
+       cr.paint()
 
-#      cr.save()
-#      x3=x + (self.config.iconspace/2)
-#      y3=y + (self.config.iconspace/2)
-#      cr.set_source_pixbuf(icon2, x3, y3)
-#
-#      cr.paint()
-#      cr.restore()
+       self.sicon=s
 
+       return(s)
 
-       # Width is the iconsize plus the empty border around the icon
-       w=self.config.iconsize + self.config.iconspace
+    def get_paint_icon(self):
+       """ Return the icon to paint as a surface. The icon is rotated
+       as needed. The result is cached. """
+       angle=self.angle
 
-       # This is used to locate the center of the surface
-       dx=int(w/2)
+       if self.timePressed() <= self.presstime or self.ispressed:
+            t=self.timePressed()
+           pressed=True
+       else:
+            t=0
+           pressed=False
+
+       if not pressed and self.cached_icons.has_key(angle):
+           return(self.cached_icons[angle])
+
+        w=self.config.iconsize + self.config.iconspace
+       s=cairo.ImageSurface(cairo.FORMAT_ARGB32, w, w)
+       cr0=cairo.Context(s)
+       cr=gtk.gdk.CairoContext(cr0)
+
+       # Paint the background
+        s2=self.mkbg(t)
+       cr.save()
+       cr.set_source_surface(s2, 0, 0)
+       cr.paint()
+       cr.restore()
+
+       # If there is no icon then don't do anything more
+       if self.icon!=None:
+           # Get the icon as a surface (get_sicon() will cache the surface)
+           sicon=self.get_sicon()
+
+           # Width is the iconsize plus the empty border around the icon
+           #w=self.config.iconsize + self.config.iconspace
+
+           # This is used to locate the center of the surface
+           dx=int(w/2)
+
+           # This is the delta from the center where icons are drawn
+           dx2=int(self.config.iconsize/2)
+
+#          cr.save()
 
-       # This is the delta from the center where icons are drawn
-       dx2=int(self.config.iconsize/2)
+           # A transformation matrix with dx/dy set to point to the center
+           m=cairo.Matrix(1, 0, 0, 1, dx, dx)
+           cr.set_matrix(m)
+           # Transform degrees to rads
+           rot=-1 * pi * 2 * self.angle / 360
+           cr.rotate(rot)
+           # Draw the icon
+           cr.set_source_surface(sicon, -dx2, -dx2)    # Faster than pixbuf
+#      cr.set_source_pixbuf(icon2, -dx2, -dx2)
+           cr.paint()
 
-       # A surface to draw on
-       t_s=cairo.ImageSurface(cairo.FORMAT_ARGB32, w, w)
+#          cr.restore()
 
-       # And a context to draw
-       t_cr0=cairo.Context(t_s)
-       t_cr=gtk.gdk.CairoContext(t_cr0)
+       if not pressed:
+           self.cached_icons[angle]=s
 
-       # A transformation matrix with dx/dy set to point to the center
-       m=cairo.Matrix(1, 0, 0, 1, dx, dx)
-       t_cr.set_matrix(m)
-       # Transform degrees to rads
-       rot=-1 * pi * 2 * self.angle / 360
-       t_cr.rotate(rot)
-       # Draw the icon
-       t_cr.set_source_pixbuf(icon2, -dx2, -dx2)
-       t_cr.paint()
+       return(s)
+
+
+    def draw(self, cr, x, y):
+       self.draw_queued=False
+       self.x=x
+       self.y=y
+
+       if self.icon==None and not self.isconfig:
+           return
 
-       # Draw the rotated icon on the main cairo context
        cr.save()
-       cr.set_source_surface(t_s, x, y)
+       s=self.get_paint_icon()
+       cr.set_source_surface(s, x, y)
        cr.paint()
+
        cr.restore()
 
        return(False)
@@ -261,7 +328,7 @@ class Icon(gobject.GObject):
            window=self.window
        else:
            self.window=window
-       
+
        if window==None:
            return
 
index 0278dde..3571736 100755 (executable)
@@ -110,16 +110,22 @@ class IconGrid(object):   #(gobject.GObject):
            #self.queue_draw()
            self.angle_timer_start=time.time()
            gobject.timeout_add(20, self.timerAngle)
-
+        else:
+            if self.mode=='l':
+                self.setAngle(0)
+            else:
+                self.setAngle(90)
+                
     def timerAngle(self):
-       self.queue_draw()
-
        if self.angle_timer_start==0:
-           self.angle_timer_start=time.time()
+           self.angle_timer_start=time.time()-0.05
 
        dt=time.time()-self.angle_timer_start
 
-       da=90.0*dt/0.5
+       # Duration of the rotation effect
+       rotation_time=0.5
+
+       da=90.0*dt/rotation_time
 
        if self.mode=='l':
            angle=90-da
@@ -135,7 +141,8 @@ class IconGrid(object):     #(gobject.GObject):
        else:
            ret=True
 
-       self.setAngle(angle)
+       if self.setAngle(angle):
+           self.queue_draw()
 
        if ret==False:
            self.angle_timer_start=0
@@ -186,9 +193,14 @@ class IconGrid(object):    #(gobject.GObject):
            ico.draw(cr, x2, y2)
 
     def setAngle(self, angle):
+       """ Return True/False indicating that angle has changed """
+       ret=False
        for x,y in self.icons:
            ic=self.icons.get(x,y)
-           ic.setAngle(angle)
+           if ic.setAngle(angle):
+               ret=True
+
+       return(ret)
 
     def do_expose_event(self, event):
        cr=self.window.cairo_create()