Experimenting widget animation with Fbo

PyMT provide 2 way to make animations :

They are things we cannot do actually :

So, i was thinking to use Fbo to make more complex animation on a widget.

Before starting the how-to, here a demo-video.

▶ Watch the video on Vimeo

PyMT - New desktop with menu animation

Create a new widget

Let’s start with a simple widget :

class Menu(MTKineticList):
  def __init__(self, **kwargs):
    kwargs.setdefault('title', None)
    kwargs.setdefault('searchable', False)
    kwargs.setdefault('deletable', False)
    kwargs.setdefault('padding_x', 0)
super(Menu, self).__init__(**kwargs)
self.size = (220, 200)
for x in xrange(12):
      self.add_widget(MTKineticItem(label=str(x), size=(220, 30)))

A simple kinetic list 12 buttons in.

First step, draw on a Fbo

Note: Fbo is an acronym for FrameBuffer Object. You can learn about is at : http://en.wikipedia.org/wiki/Framebuffer_Object

So, an idea would be to draw the content of the children on a Fbo, and draw the Fbo with scale or alpha changed. So, in init(), we can add :

self.fbo = Fbo(size=self.size)

And rework the on_draw() method :

def on_draw(self):
    # draw the widget content on a fbo
    with self.fbo:
      super(Menu, self).on_draw()
# draw the content on fbo on the window
    drawTexturedRectangle(texture=self.fbo.texture, pos=self.pos, size=self.size)

And don’t forget about widget resizing :

def on_resize(self, w, h):
    if self.fbo.size != self.size:
      self.fbo = Fbo(size=self.size)

Result : failed. The drawing on the fbo don’t start at (0, 0), but at self.pos : the widget is translated inside the Fbo.

Second step: fixing translation

So, when drawing in Fbo, the easiest thing to do is to cancel the translation. We do it in OpenGL :

def on_draw(self):
    # push the actual modelview matrix
# + draw the widget content on a fbo
    with DO(gx_matrix, self.fbo):
      # untranslate the widget position
      glTranslatef(-self.x, -self.y, 0)
# draw the widget content + his children
super(Menu, self).on_draw()
# draw the content on fbo on the window
    drawTexturedRectangle(texture=self.fbo.texture, pos=self.pos, size=self.size)

Result : win ! The drawing is now ok on the Fbo. We can play with the texture :)

Third step: playing with alpha

When the menu is created, i would like to animate it, like a Fade-in effect. We’ll start with an alpha = 0, and progress the value to 1. In init() method, we’ll add an alpha property, and set it to 0 :

self.alpha = 0

And animate it in on_draw(), by using getFrameDt(). The method getFrameDt() return the delta time between the last 2 frames : the animation will have the same speed for a 60FPS as for 873FPS.

def on_draw(self):
    self.alpha += getFrameDt()
# limit alpha to 0 - 1
self.alpha = min(self.alpha, 1)
# ... draw on fbo things ...
    set_color(1, 1, 1, self.alpha)
    drawTexturedRectangle(texture=self.fbo.texture, pos=self.pos, size=self.size)

Result : the magic happen ! It’s a success. But we could do a last optimisation about drawing : if the alpha is 1, why drawing on Fbo ? It’s not needed :)

Final version of widget

The last version contain the optimisation. Check it out !

from pymt import *
from pyglet.gl import *
 
class Menu(MTKineticList):
  def __init__(self, **kwargs):
    kwargs.setdefault('title', None)
    kwargs.setdefault('searchable', False)
    kwargs.setdefault('deletable', False)
    kwargs.setdefault('padding_x', 0)
super(Menu, self).__init__(**kwargs)
self.size = (220, 200)
for x in xrange(12):
      self.add_widget(MTKineticItem(label=str(x), size=(220, 30)))
self.fbo = Fbo(size=self.size)
self.alpha = 0
 
  def on_resize(self, w, h):
    self.fbo = Fbo(size=self.size)
 
  def on_draw(self):
    self.alpha += getFrameDt()
# limit alpha to 0 - 1
self.alpha = min(self.alpha, 1)
if self.alpha < 1:
      # push the actual modelview matrix
# + draw the widget content on a fbo
      with DO(gx_matrix, self.fbo):
        # untranslate the widget position
        glTranslatef(-self.x, -self.y, 0)
# draw the widget content + his children
super(Menu, self).on_draw()
# set alpha
      set_color(1, 1, 1, self.alpha)
# draw the content on fbo on the window
      drawTexturedRectangle(texture=self.fbo.texture, pos=self.pos, size=self.size)
else:
      # alpha is 1, no transparency. don't use fbo !
super(Menu, self).on_draw()
 
if __name__ == '__main__':
	m = MTWindow()
	m.add_widget(Menu())
	runTouchApp()