Skip to content

Experimenting widget animation with Fbo

PyMT provide 2 way to make animations :

  • property animation with Animation() class, can be created with start_animation() / stop_animation(),
  • inner animation, that watch changes on a property and start automatically an animation.

They are things we cannot do actually :

  • Fade in/out a widget and their children
  • Scale the widget content

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

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


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()