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