ClutterTransitionGroup and ClutterKeyframeTransition don't play well together
I'd like to animate a dynamic set of actors driven by one timeline, such that they all animate in sync with each other. I thought that ClutterTransitionGroup
would be ideal for this, but adding and removing ClutterKeyframeTransition
s from the group goes very poorly. It seems there's some extra state being maintained in ClutterKeyframeTransition
that ClutterTransitionGroup
doesn't update when it forces an advance, which causes the animations to wildly deviate from their expected paths.
Here's the closest I've come to getting this to work; it contains two workarounds (commented) that I'm not sure ought to be necessary, and even with those the animation doesn't quite behave correctly—when the actors join the animation, you can often see them rapidly rotate much farther than the keyframes should allow them to.
Am I doing this wrong? Are these objects somehow not intended to be used with each other in this way? Or are these all actual bugs?
#include <clutter/clutter.h>
#define BOX_COUNT 5
static ClutterTransition *group;
static ClutterTransition *shakes[BOX_COUNT];
static gboolean is_shake_on[BOX_COUNT];
gboolean
toggle_transition(gpointer data) {
gsize i = (gsize)data;
is_shake_on[i] = !is_shake_on[i];
if (is_shake_on[i]) {
clutter_transition_group_add_transition(CLUTTER_TRANSITION_GROUP(group), shakes[i]);
/* Without this, the animations don't sync up (is this intentional or a bug?)
*/
clutter_timeline_advance(CLUTTER_TIMELINE(shakes[i]), clutter_timeline_get_elapsed_time(CLUTTER_TIMELINE(group)));
/* Without this, Clutter-CRITICAL "assertion 'CLUTTER_IS_INTERVAL (interval)' failed"
* spam and animations break (surely this is a bug?)
*/
g_signal_emit_by_name(shakes[i], "started");
} else {
clutter_transition_group_remove_transition(CLUTTER_TRANSITION_GROUP(group), shakes[i]);
}
g_timeout_add(2000, toggle_transition, (gpointer)i);
return FALSE;
}
int
main(int argc, char *argv[])
{
ClutterActor *stage, *rect;
ClutterTransition *shake;
gfloat stage_width, stage_height;
GError *error = NULL;
gsize i;
if (clutter_init_with_args(&argc, &argv, NULL, NULL, NULL, &error) != CLUTTER_INIT_SUCCESS) {
return 1;
}
stage = clutter_stage_new();
clutter_actor_set_background_color(stage, CLUTTER_COLOR_Black);
g_signal_connect(stage, "destroy", G_CALLBACK(clutter_main_quit), NULL);
clutter_actor_get_size(stage, &stage_width, &stage_height);
group = clutter_transition_group_new();
for (i = 0; i < BOX_COUNT; i++) {
rect = clutter_actor_new();
clutter_actor_set_width(rect, 32);
clutter_actor_set_height(rect, 32);
clutter_actor_set_background_color(rect, CLUTTER_COLOR_White);
clutter_actor_add_child(stage, rect);
clutter_actor_set_position(rect, stage_width / 2 - 16 + 48 * ((gint)i - (BOX_COUNT - 1) / 2), stage_height / 2 - 16);
clutter_actor_set_pivot_point(rect, 0.5, 0.5);
shake = clutter_keyframe_transition_new("rotation-angle-z");
clutter_transition_set_from(shake, G_TYPE_DOUBLE, 0);
clutter_transition_set_to(shake, G_TYPE_DOUBLE, 0);
clutter_timeline_set_duration(CLUTTER_TIMELINE(shake), 2000);
clutter_keyframe_transition_set(CLUTTER_KEYFRAME_TRANSITION(shake),
G_TYPE_DOUBLE,
5,
0.05, 8.0, CLUTTER_EASE_OUT_QUAD,
0.15, -8.0, CLUTTER_EASE_IN_OUT_QUAD,
0.25, 8.0, CLUTTER_EASE_IN_OUT_QUAD,
0.35, -8.0, CLUTTER_EASE_IN_OUT_QUAD,
0.40, 0.0, CLUTTER_EASE_IN_QUAD);
clutter_transition_set_animatable(shake, CLUTTER_ANIMATABLE(rect));
shakes[i] = shake;
is_shake_on[i] = FALSE;
g_timeout_add(i * 500, toggle_transition, (gpointer)i);
}
clutter_timeline_set_duration(CLUTTER_TIMELINE(group), 2000);
clutter_timeline_set_repeat_count(CLUTTER_TIMELINE(group), -1);
clutter_timeline_start(CLUTTER_TIMELINE(group));
clutter_actor_show(stage);
clutter_main();
return EXIT_SUCCESS;
}