2007-03-12 12:39:29 +01:00
|
|
|
# Copyright (C) 2007, Red Hat, Inc.
|
|
|
|
#
|
|
|
|
# This library is free software; you can redistribute it and/or
|
|
|
|
# modify it under the terms of the GNU Lesser General Public
|
|
|
|
# License as published by the Free Software Foundation; either
|
|
|
|
# version 2 of the License, or (at your option) any later version.
|
|
|
|
#
|
|
|
|
# This library is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
# Lesser General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU Lesser General Public
|
|
|
|
# License along with this library; if not, write to the
|
|
|
|
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
|
|
# Boston, MA 02111-1307, USA.
|
|
|
|
|
2008-10-28 14:19:01 +01:00
|
|
|
"""
|
2015-08-02 00:47:48 +02:00
|
|
|
The animator module provides a simple framwork to create animations.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
Animate the size of a window::
|
|
|
|
|
|
|
|
from gi.repository import Gtk
|
|
|
|
from sugar3.graphics.animator import Animator, Animation
|
|
|
|
|
|
|
|
# Construct a 5 second animator
|
|
|
|
animator = Animator(5)
|
|
|
|
|
|
|
|
# Construct a window to animate
|
|
|
|
w = Gtk.Window()
|
|
|
|
w.connect('destroy', Gtk.main_quit)
|
|
|
|
# Start the animation when the window is shown
|
|
|
|
w.connect('realize', lambda self: animator.start())
|
|
|
|
w.show()
|
|
|
|
|
|
|
|
# Create an animation subclass to animate the widget
|
|
|
|
class SizeAnimation(Animation):
|
|
|
|
def __init__(self):
|
|
|
|
# Tell the animation to give us values between 20 and
|
|
|
|
# 420 during the animation
|
|
|
|
Animation.__init__(self, 20, 420)
|
|
|
|
|
|
|
|
def next_frame(self, frame):
|
|
|
|
size = int(frame)
|
|
|
|
w.resize(size, size)
|
|
|
|
# Add the animation the the animator
|
|
|
|
animation = SizeAnimation()
|
|
|
|
animator.add(animation)
|
|
|
|
|
|
|
|
# The animation needs to run inside a GObject main loop
|
|
|
|
Gtk.main()
|
|
|
|
|
2008-10-28 14:19:01 +01:00
|
|
|
STABLE.
|
|
|
|
"""
|
|
|
|
|
2007-03-12 12:39:29 +01:00
|
|
|
import time
|
|
|
|
|
2011-11-15 19:29:07 +01:00
|
|
|
from gi.repository import GObject
|
2013-05-06 18:18:10 +02:00
|
|
|
from gi.repository import GLib
|
2007-03-12 12:39:29 +01:00
|
|
|
|
2007-03-13 00:25:41 +01:00
|
|
|
EASE_OUT_EXPO = 0
|
2009-08-25 21:12:40 +02:00
|
|
|
EASE_IN_EXPO = 1
|
|
|
|
|
2007-03-12 12:39:29 +01:00
|
|
|
|
2011-11-15 19:29:07 +01:00
|
|
|
class Animator(GObject.GObject):
|
2015-08-02 00:47:48 +02:00
|
|
|
'''
|
|
|
|
The animator class manages the the timing for calling the
|
|
|
|
animations. The animations can be added using the `add` function
|
|
|
|
and then started with the `start` function. If multiple animations
|
|
|
|
are added, then they will be played back at the same time and rate
|
|
|
|
as each other.
|
|
|
|
|
|
|
|
The `completed` signal is emitted upon the completion of the
|
|
|
|
animation and also when the `stop` function is called.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
duration (float): the duration of the animation in seconds
|
|
|
|
fps (int, optional): the number of animation callbacks to make
|
|
|
|
per second (frames per second)
|
|
|
|
easing (int): the desired easing mode, either `EASE_OUT_EXPO`
|
|
|
|
or `EASE_IN_EXPO`
|
|
|
|
|
|
|
|
.. note::
|
|
|
|
|
|
|
|
When creating an animation, take into account the limited cpu power
|
|
|
|
on some devices, such as the XO. Setting the fps too high on can
|
|
|
|
use signifigant cpu usage on the XO.
|
|
|
|
'''
|
2009-08-25 21:12:40 +02:00
|
|
|
|
2007-03-12 16:57:52 +01:00
|
|
|
__gsignals__ = {
|
2011-11-15 19:29:07 +01:00
|
|
|
'completed': (GObject.SignalFlags.RUN_FIRST, None, ([])),
|
2007-03-12 16:57:52 +01:00
|
|
|
}
|
2009-08-25 19:55:48 +02:00
|
|
|
|
2008-04-19 00:14:11 +02:00
|
|
|
def __init__(self, duration, fps=20, easing=EASE_OUT_EXPO):
|
2011-11-15 19:29:07 +01:00
|
|
|
GObject.GObject.__init__(self)
|
2007-03-12 12:39:29 +01:00
|
|
|
self._animations = []
|
2008-04-19 00:14:11 +02:00
|
|
|
self._duration = duration
|
2007-03-12 12:39:29 +01:00
|
|
|
self._interval = 1.0 / fps
|
|
|
|
self._easing = easing
|
|
|
|
self._timeout_sid = 0
|
2008-04-19 00:14:11 +02:00
|
|
|
self._start_time = None
|
2007-03-12 12:39:29 +01:00
|
|
|
|
|
|
|
def add(self, animation):
|
2015-08-02 00:47:48 +02:00
|
|
|
'''
|
|
|
|
Add an animation to this animator
|
|
|
|
|
|
|
|
Args:
|
|
|
|
animation (:class:`sugar3.graphics.animator.Animation`):
|
|
|
|
the animation instance to add
|
|
|
|
'''
|
2007-03-12 12:39:29 +01:00
|
|
|
self._animations.append(animation)
|
|
|
|
|
2007-04-16 11:40:28 +02:00
|
|
|
def remove_all(self):
|
2015-08-02 00:47:48 +02:00
|
|
|
'''
|
|
|
|
Remove all animations and stop this animator
|
|
|
|
'''
|
2007-04-16 11:40:28 +02:00
|
|
|
self.stop()
|
|
|
|
self._animations = []
|
|
|
|
|
2007-03-12 12:39:29 +01:00
|
|
|
def start(self):
|
2015-08-02 00:47:48 +02:00
|
|
|
'''
|
|
|
|
Start the animation running. This will stop and restart the
|
|
|
|
animation if the animation is currently running
|
|
|
|
'''
|
2007-03-12 12:39:29 +01:00
|
|
|
if self._timeout_sid:
|
|
|
|
self.stop()
|
|
|
|
|
|
|
|
self._start_time = time.time()
|
2013-05-06 18:18:10 +02:00
|
|
|
self._timeout_sid = GLib.timeout_add(
|
2013-05-18 04:27:04 +02:00
|
|
|
int(self._interval * 1000), self._next_frame_cb)
|
2007-03-12 12:39:29 +01:00
|
|
|
|
|
|
|
def stop(self):
|
2015-08-02 00:47:48 +02:00
|
|
|
'''
|
|
|
|
Stop the animation and emit the `completed` signal
|
|
|
|
'''
|
2007-03-12 12:39:29 +01:00
|
|
|
if self._timeout_sid:
|
2011-11-15 19:29:07 +01:00
|
|
|
GObject.source_remove(self._timeout_sid)
|
2007-03-12 12:39:29 +01:00
|
|
|
self._timeout_sid = 0
|
2007-04-16 11:40:28 +02:00
|
|
|
self.emit('completed')
|
2007-03-12 12:39:29 +01:00
|
|
|
|
|
|
|
def _next_frame_cb(self):
|
2008-04-19 00:14:11 +02:00
|
|
|
current_time = min(self._duration, time.time() - self._start_time)
|
2007-03-12 17:47:09 +01:00
|
|
|
current_time = max(current_time, 0.0)
|
|
|
|
|
2007-03-12 12:39:29 +01:00
|
|
|
for animation in self._animations:
|
2008-04-19 00:14:11 +02:00
|
|
|
animation.do_frame(current_time, self._duration, self._easing)
|
2007-03-12 12:39:29 +01:00
|
|
|
|
2008-04-19 00:14:11 +02:00
|
|
|
if current_time == self._duration:
|
2007-03-12 16:57:52 +01:00
|
|
|
self.stop()
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
return True
|
2007-03-12 12:39:29 +01:00
|
|
|
|
2009-08-25 21:12:40 +02:00
|
|
|
|
2007-03-12 12:39:29 +01:00
|
|
|
class Animation(object):
|
2015-08-02 00:47:48 +02:00
|
|
|
'''
|
|
|
|
The animation class is a base class for creating an animation.
|
|
|
|
It should be subclassed. Subclasses should specify a `next_frame`
|
|
|
|
function to set the required properties based on the animation
|
|
|
|
progress. The range of the `frame` value passed to the `next_frame`
|
|
|
|
function is defined by the `start` and `end` values.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
start (float): the first `frame` value for the `next_frame` method
|
|
|
|
end (float): the last `frame` value for the `next_frame` method
|
|
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
|
|
|
|
# Create an animation subclass
|
|
|
|
class MyAnimation(Animation):
|
|
|
|
def __init__(self, thing):
|
|
|
|
# Tell the animation to give us values between 0.0 and
|
|
|
|
# 1.0 during the animation
|
|
|
|
Animation.__init__(self, 0.0, 1.0)
|
|
|
|
self._thing = thing
|
|
|
|
|
|
|
|
def next_frame(self, frame):
|
|
|
|
# Use the `frame` value to set properties
|
|
|
|
self._thing.set_green_value(frame)
|
|
|
|
'''
|
2009-08-25 21:12:40 +02:00
|
|
|
|
2007-03-12 12:39:29 +01:00
|
|
|
def __init__(self, start, end):
|
|
|
|
self.start = start
|
|
|
|
self.end = end
|
|
|
|
|
2008-04-19 00:14:11 +02:00
|
|
|
def do_frame(self, t, duration, easing):
|
2015-08-02 00:47:48 +02:00
|
|
|
'''
|
|
|
|
This method is called by the animtor class every frame. This
|
|
|
|
method calculated the `frame` value to then call `next_frame`.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
t (float): the current time elapsed of the animation in seconds
|
|
|
|
duration (float): the length of the animation in seconds
|
|
|
|
easing (int): the easing mode passed to the animator
|
|
|
|
'''
|
2007-03-12 12:39:29 +01:00
|
|
|
start = self.start
|
|
|
|
change = self.end - self.start
|
|
|
|
|
2008-04-19 00:14:11 +02:00
|
|
|
if t == duration:
|
2007-03-13 13:19:50 +01:00
|
|
|
# last frame
|
|
|
|
frame = self.end
|
|
|
|
else:
|
|
|
|
if easing == EASE_OUT_EXPO:
|
2008-04-19 00:14:11 +02:00
|
|
|
frame = change * (-pow(2, -10 * t / duration) + 1) + start
|
2007-03-13 13:19:50 +01:00
|
|
|
elif easing == EASE_IN_EXPO:
|
2008-04-19 00:14:11 +02:00
|
|
|
frame = change * pow(2, 10 * (t / duration - 1)) + start
|
2007-03-12 12:39:29 +01:00
|
|
|
|
|
|
|
self.next_frame(frame)
|
|
|
|
|
|
|
|
def next_frame(self, frame):
|
2015-08-02 00:47:48 +02:00
|
|
|
'''
|
|
|
|
This method is called every frame and should be overriden by
|
|
|
|
subclasses.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
frame (float): a value between `start` and `end` representing
|
|
|
|
the current progress in the animation
|
|
|
|
'''
|
2007-03-12 12:39:29 +01:00
|
|
|
pass
|