From 934eee17ae34098773ff5876747061b5a1d1f1a5 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Wed, 23 Dec 2009 10:35:16 +0000 Subject: [PATCH] layout-manager: Document the animation support Add a section inside the LayoutManager class API reference documenting, with examples, how to implement animation support inside a layout manager sub-class. --- clutter/clutter-layout-manager.c | 213 +++++++++++++++++++++++++++---- 1 file changed, 190 insertions(+), 23 deletions(-) diff --git a/clutter/clutter-layout-manager.c b/clutter/clutter-layout-manager.c index 77efdd96c..850c3916a 100644 --- a/clutter/clutter-layout-manager.c +++ b/clutter/clutter-layout-manager.c @@ -36,10 +36,10 @@ * a generic container using #ClutterLayoutManager called #ClutterBox. * * Clutter provides some simple #ClutterLayoutManager sub-classes, like - * #ClutterFixedLayout and #ClutterBinLayout. + * #ClutterFlowLayout and #ClutterBinLayout. * * - * Using ClutterLayoutManager inside an Actor + * Using a Layout Manager inside an Actor * In order to use a #ClutterLayoutManager inside a #ClutterActor * sub-class you should invoke clutter_layout_manager_get_preferred_width() * inside the ClutterActor::get_preferred_width() @@ -56,10 +56,10 @@ * does not need to perform specific operations whenever a layout * manager changes: * - * g_signal_connect_swapped (layout_manager, - * "layout-changed", - * G_CALLBACK (clutter_actor_queue_relayout), - * actor); + * g_signal_connect_swapped (layout_manager, + * "layout-changed", + * G_CALLBACK (clutter_actor_queue_relayout), + * actor); * * * @@ -70,18 +70,20 @@ * #ClutterActor, so you should read the relative documentation * for subclassing * ClutterActor. - * The layout manager implementation can hold a back reference - * to the #ClutterContainer by implementing the set_container() - * virtual function. The layout manager should not hold a reference - * on the container actor, to avoid reference cycles. + * The layout manager implementation can hold a back pointer + * to the #ClutterContainer by implementing the + * set_container() virtual function. The layout manager + * should not hold a real reference (i.e. call g_object_ref()) on the + * container actor, to avoid reference cycles. * If the layout manager has properties affecting the layout * policies then it should emit the #ClutterLayoutManager::layout-changed * signal on itself by using the clutter_layout_manager_layout_changed() - * function. + * function whenever one of these properties changes. * If the layout manager has layout properties, that is properties that * should exist only as the result of the presence of a specific (layout - * manager, container actor, child actor) combination, then it should - * override the ClutterLayoutManager::get_child_meta_type() + * manager, container actor, child actor) combination, and it wishes to store + * those properties inside a #ClutterLayoutMeta then it should override the + * ClutterLayoutManager::get_child_meta_type() * virtual function to return the #GType of the #ClutterLayoutMeta sub-class * used to store the layout properties; optionally, the #ClutterLayoutManager * sub-class might also override the @@ -89,17 +91,17 @@ * function to control how the #ClutterLayoutMeta instance is created, * otherwise the default implementation will be equivalent to: * - * ClutterLayoutManagerClass *klass; - * GType meta_type; + * ClutterLayoutManagerClass *klass; + * GType meta_type; * - * klass = CLUTTER_LAYOUT_MANAGER_GET_CLASS (manager); - * meta_type = klass->get_child_meta_type (manager); + * klass = CLUTTER_LAYOUT_MANAGER_GET_CLASS (manager); + * meta_type = klass->get_child_meta_type (manager); * - * return g_object_new (meta_type, - * "manager", manager, - * "container", container, - * "actor", actor, - * NULL); + * return g_object_new (meta_type, + * "manager", manager, + * "container", container, + * "actor", actor, + * NULL); * * Where manager is the #ClutterLayoutManager, * container is the #ClutterContainer using the @@ -109,7 +111,172 @@ * * * Animating a ClutterLayoutManager - * ... + * A layout manager is used to let a #ClutterContainer take complete + * ownership over the layout (that is: the position and sizing) of its + * children; this means that using the Clutter animation API, like + * clutter_actor_animate(), to animate the position and sizing of a child of + * a layout manager it is not going to work properly, as the animation will + * automatically override any setting done by the layout manager + * itself. + * It is possible for a #ClutterLayoutManager sub-class to animate its + * children layout by using the base class animation support. The + * #ClutterLayoutManager animation support consists of three virtual + * functions: begin_animation(), + * get_animation_progress() and + * end_animation(). + * + * + * begin_animation (duration, easing) + * This virtual function is invoked when the layout + * manager should begin an animation. The implementation should set up + * the state for the animation and create the ancillary objects for + * animating the layout. The default implementation creates a + * #ClutterTimeline for the given duration and a #ClutterAlpha binding + * the timeline to the given easing mode. This function returns a + * #ClutterAlpha which should be used to control the animation from + * the caller perspective. + * + * + * get_animation_progress() + * This virtual function should be invoked when animating + * a layout manager. It returns the progress of the animation, using the + * same semantics as the #ClutterAlpha:alpha value. + * + * + * end_animation() + * This virtual function is invoked when the animation of + * a layout manager ends, and it is meant to be used for bookkeeping the + * objects created in the begin_animation() + * function. The default implementation will call it implicitly when the + * timeline is complete. + * + * + * The simplest way to animate a layout is to create a #ClutterTimeline + * inside the begin_animation() virtual function, along + * with a #ClutterAlpha, and for each #ClutterTimeline::new-frame signal + * emission call clutter_layout_manager_layout_changed(), which will cause a + * relayout. The #ClutterTimeline::completed signal emission should cause + * clutter_layout_manager_end_animation() to be called. The default + * implementation provided internally by #ClutterLayoutManager does exactly + * this, so most sub-classes should either not override any animation-related + * virtual function or simply override begin_animation() + * and end_animation() to set up ad hoc state, and then + * chain up to the parent's implementation. + * + * Animation of a Layout Manager + * The code below shows how a #ClutterLayoutManager sub-class should + * provide animating the allocation of its children from within the + * allocate() virtual function implementation. The + * animation is computed between the last stable allocation performed + * before the animation started and the desired final allocation. + * The is_animating variable is stored inside the + * #ClutterLayoutManager sub-class and it is updated by overriding the + * begin_animation() and + * end_animation() virtual functions and chaining up + * to the base class implementation. + * The last stable allocation is stored within a #ClutterLayoutMeta + * sub-class used by the implementation. + * + * static void + * my_layout_manager_allocate (ClutterLayoutManager *manager, + * ClutterContainer *container, + * const ClutterActorBox *allocation, + * ClutterAllocationFlags flags) + * { + * MyLayoutManager *self = MY_LAYOUT_MANAGER (manager); + * GList *children, *l; + * + * children = clutter_container_get_children (container); + * + * for (l = children; l != NULL; l = l->next) + * { + * ClutterActor *child = l->data; + * ClutterLayoutMeta *meta; + * MyLayoutMeta *my_meta; + * + * /* retrieve the layout meta-object */ + * meta = clutter_layout_manager_get_child_meta (manager, + * container, + * child); + * my_meta = MY_LAYOUT_META (meta); + * + * /* compute the desired allocation for the child */ + * compute_allocation (self, my_meta, child, + * allocation, flags, + * &child_box); + * + * /* this is the additional code that deals with the animation + * * of the layout manager + * */ + * if (!self->is_animating) + * { + * /* store the last stable allocation for later use */ + * my_meta->last_alloc = clutter_actor_box_copy (&child_box); + * } + * else + * { + * ClutterActorBox end = { 0, }; + * gdouble p; + * + * /* get the progress of the animation */ + * p = clutter_layout_manager_get_animation_progress (manager); + * + * if (my_meta->last_alloc != NULL) + * { + * /* copy the desired allocation as the final state */ + * end = child_box; + * + * /* then interpolate the initial and final state + * * depending on the progress of the animation, + * * and put the result inside the box we will use + * * to allocate the child + * */ + * clutter_actor_box_interpolate (my_meta->last_alloc, + * &end, + * p, + * &child_box); + * } + * else + * { + * /* if there is no stable allocation then the child was + * * added while animating; one possible course of action + * * is to just bail out and fall through to the allocation + * * to position the child directly at its final state + * */ + * my_meta->last_alloc = + * clutter_actor_box_copy (&child_box); + * } + * } + * + * /* allocate the child */ + * clutter_actor_allocate (child, &child_box, flags); + * } + * + * g_list_free (children); + * } + * + * + * Sub-classes of #ClutterLayoutManager that support animations of the + * layout changes should call clutter_layout_manager_begin_animation() + * whenever a layout property changes value, e.g.: + * + * + * if (self->orientation != new_orientation) + * { + * ClutterLayoutManager *manager; + * + * self->orientation = new_orientation; + * + * manager = CLUTTER_LAYOUT_MANAGER (self); + * clutter_layout_manager_layout_changed (manager); + * clutter_layout_manager_begin_animation (manager, 500, CLUTTER_LINEAR); + * + * g_object_notify (G_OBJECT (self), "orientation"); + * } + * + * + * The code above will animate a change in the + * orientation layout property of a layout manager. * * * #ClutterLayoutManager is available since Clutter 1.2