panel: programmatic anim. control of AnimatedIcon

The AnimatedIcon does not have an API for controlling the animation but
relies on the :visible property changes to start and stop a timeout used
to update the frame.

This has the inconvenient of having a side effect when visible is set to
true multiple times, and is not really the API expected from such
component. Also, there is a race if it is displayed before the images
are loaded: there is no child yet and thus we get this._frame = NaN
which leads to a crash.

Switch to a play/stop API instead, and add a load event callback to the
TextureCache.load_slice_image to exactly know when we can start using
the images.

https://bugzilla.gnome.org/show_bug.cgi?id=687583
This commit is contained in:
Stéphane Démurget 2012-11-05 23:11:27 +01:00
parent f084011a61
commit c48a246ccb
3 changed files with 51 additions and 18 deletions

View File

@ -82,26 +82,34 @@ const AnimatedIcon = new Lang.Class({
_init: function(name, size) { _init: function(name, size) {
this.actor = new St.Bin({ visible: false }); this.actor = new St.Bin({ visible: false });
this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
this.actor.connect('notify::visible', Lang.bind(this, this._onVisibleNotify));
this._isLoaded = false;
this._isPlaying = false;
this._timeoutId = 0; this._timeoutId = 0;
this._frame = 0; this._frame = 0;
this._animations = St.TextureCache.get_default().load_sliced_image (global.datadir + '/theme/' + name, size, size); this._animations = St.TextureCache.get_default().load_sliced_image (global.datadir + '/theme/' + name, size, size,
Lang.bind(this, this._animationsLoaded));
this.actor.set_child(this._animations); this.actor.set_child(this._animations);
}, },
_disconnectTimeout: function() { play: function() {
if (this._isLoaded && this._timeoutId == 0) {
if (this._frame == 0)
this._showFrame(0);
this._timeoutId = Mainloop.timeout_add(ANIMATED_ICON_UPDATE_TIMEOUT, Lang.bind(this, this._update));
}
this._isPlaying = true;
},
stop: function() {
if (this._timeoutId > 0) { if (this._timeoutId > 0) {
Mainloop.source_remove(this._timeoutId); Mainloop.source_remove(this._timeoutId);
this._timeoutId = 0; this._timeoutId = 0;
} }
},
_onVisibleNotify: function() { this._isPlaying = false;
if (this.actor.visible)
this._timeoutId = Mainloop.timeout_add(ANIMATED_ICON_UPDATE_TIMEOUT, Lang.bind(this, this._update));
else
this._disconnectTimeout();
}, },
_showFrame: function(frame) { _showFrame: function(frame) {
@ -121,8 +129,17 @@ const AnimatedIcon = new Lang.Class({
return true; return true;
}, },
_animationsLoaded: function() {
this._isLoaded = true;
if (this._isPlaying) {
this._showFrame(0);
this._timeoutId = Mainloop.timeout_add(ANIMATED_ICON_UPDATE_TIMEOUT, Lang.bind(this, this._update));
}
},
_onDestroy: function() { _onDestroy: function() {
this._disconnectTimeout(); this.stop();
} }
}); });
@ -367,6 +384,7 @@ const AppMenuButton = new Lang.Class({
transition: "easeOutQuad", transition: "easeOutQuad",
onCompleteScope: this, onCompleteScope: this,
onComplete: function() { onComplete: function() {
this._spinner.stop();
this._spinner.actor.opacity = 255; this._spinner.actor.opacity = 255;
this._spinner.actor.hide(); this._spinner.actor.hide();
} }
@ -376,6 +394,7 @@ const AppMenuButton = new Lang.Class({
startAnimation: function() { startAnimation: function() {
this._stop = false; this._stop = false;
this.actor.reactive = false; this.actor.reactive = false;
this._spinner.play();
this._spinner.actor.show(); this._spinner.actor.show();
}, },

View File

@ -1073,6 +1073,8 @@ typedef struct {
gchar *path; gchar *path;
gint grid_width, grid_height; gint grid_width, grid_height;
ClutterActor *actor; ClutterActor *actor;
GFunc load_callback;
gpointer load_callback_data;
} AsyncImageData; } AsyncImageData;
static void static void
@ -1089,6 +1091,7 @@ on_sliced_image_loaded (GObject *source_object,
GAsyncResult *res, GAsyncResult *res,
gpointer user_data) gpointer user_data)
{ {
GObject *cache = source_object;
AsyncImageData *data = (AsyncImageData *)user_data; AsyncImageData *data = (AsyncImageData *)user_data;
GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res); GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
GList *list; GList *list;
@ -1102,6 +1105,9 @@ on_sliced_image_loaded (GObject *source_object,
clutter_actor_hide (actor); clutter_actor_hide (actor);
clutter_actor_add_child (data->actor, actor); clutter_actor_add_child (data->actor, actor);
} }
if (data->load_callback != NULL)
data->load_callback (cache, data->load_callback_data);
} }
static void static void
@ -1156,6 +1162,8 @@ load_sliced_image (GSimpleAsyncResult *result,
* @path: Path to a filename * @path: Path to a filename
* @grid_width: Width in pixels * @grid_width: Width in pixels
* @grid_height: Height in pixels * @grid_height: Height in pixels
* @load_callback: (scope async) (allow-none): Function called when the image is loaded, or %NULL
* @user_data: Data to pass to the load callback
* *
* This function reads a single image file which contains multiple images internally. * This function reads a single image file which contains multiple images internally.
* The image file will be divided using @grid_width and @grid_height; * The image file will be divided using @grid_width and @grid_height;
@ -1165,10 +1173,12 @@ load_sliced_image (GSimpleAsyncResult *result,
* Returns: (transfer none): A new #ClutterActor * Returns: (transfer none): A new #ClutterActor
*/ */
ClutterActor * ClutterActor *
st_texture_cache_load_sliced_image (StTextureCache *cache, st_texture_cache_load_sliced_image (StTextureCache *cache,
const gchar *path, const gchar *path,
gint grid_width, gint grid_width,
gint grid_height) gint grid_height,
GFunc load_callback,
gpointer user_data)
{ {
AsyncImageData *data; AsyncImageData *data;
GSimpleAsyncResult *result; GSimpleAsyncResult *result;
@ -1179,6 +1189,8 @@ st_texture_cache_load_sliced_image (StTextureCache *cache,
data->grid_height = grid_height; data->grid_height = grid_height;
data->path = g_strdup (path); data->path = g_strdup (path);
data->actor = actor; data->actor = actor;
data->load_callback = load_callback;
data->load_callback_data = user_data;
g_object_ref (G_OBJECT (actor)); g_object_ref (G_OBJECT (actor));
result = g_simple_async_result_new (G_OBJECT (cache), on_sliced_image_loaded, data, st_texture_cache_load_sliced_image); result = g_simple_async_result_new (G_OBJECT (cache), on_sliced_image_loaded, data, st_texture_cache_load_sliced_image);

View File

@ -69,10 +69,12 @@ GType st_texture_cache_get_type (void) G_GNUC_CONST;
StTextureCache* st_texture_cache_get_default (void); StTextureCache* st_texture_cache_get_default (void);
ClutterActor * ClutterActor *
st_texture_cache_load_sliced_image (StTextureCache *cache, st_texture_cache_load_sliced_image (StTextureCache *cache,
const gchar *path, const gchar *path,
gint grid_width, gint grid_width,
gint grid_height); gint grid_height,
GFunc load_callback,
gpointer user_data);
ClutterActor *st_texture_cache_bind_pixbuf_property (StTextureCache *cache, ClutterActor *st_texture_cache_bind_pixbuf_property (StTextureCache *cache,
GObject *object, GObject *object,