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