actor: Add ClutterActorIter

Iterating over children and ancestors of an actor is a relatively common
operation. Currently, you only have one option: start a for() loop, get
the first child of the actor, and advance to the next sibling for the
list of children; or start a for() loop and advance to the parent of the
actor.

These operations can be easily done through the ClutterActor API, but
they all require going through the public API, and performing multiple
type checks on the arguments.

Along with the DOM API, it would be nice to have an ancillary, utility
API that uses an iterator structure to hold the state, and can be
advanced in a loop.

https://bugzilla.gnome.org/show_bug.cgi?id=668669
This commit is contained in:
Emmanuele Bassi 2012-01-25 15:27:57 +00:00
parent fa856e3f5e
commit 58ffcfb10e
8 changed files with 385 additions and 13 deletions

View File

@ -395,6 +395,14 @@ struct _ClutterActorPrivate
gint n_children; gint n_children;
/* tracks whenever the children of an actor are changed; the
* age is incremented by 1 whenever an actor is added or
* removed. the age is not incremented when the first or the
* last child pointers are changed, or when grandchildren of
* an actor are changed.
*/
gint age;
gchar *name; /* a non-unique name, used for debugging */ gchar *name; /* a non-unique name, used for debugging */
guint32 id; /* unique id, used for backward compatibility */ guint32 id; /* unique id, used for backward compatibility */
@ -3500,6 +3508,8 @@ clutter_actor_remove_child_internal (ClutterActor *self,
self->priv->n_children -= 1; self->priv->n_children -= 1;
self->priv->age += 1;
/* clutter_actor_reparent() will emit ::parent-set for us */ /* clutter_actor_reparent() will emit ::parent-set for us */
if (emit_parent_set && !CLUTTER_ACTOR_IN_REPARENT (child)) if (emit_parent_set && !CLUTTER_ACTOR_IN_REPARENT (child))
g_signal_emit (child, actor_signals[PARENT_SET], 0, self); g_signal_emit (child, actor_signals[PARENT_SET], 0, self);
@ -10040,6 +10050,8 @@ clutter_actor_add_child_internal (ClutterActor *self,
self->priv->n_children += 1; self->priv->n_children += 1;
self->priv->age += 1;
/* if push_internal() has been called then we automatically set /* if push_internal() has been called then we automatically set
* the flag on the actor * the flag on the actor
*/ */
@ -15770,3 +15782,177 @@ clutter_actor_get_last_child (ClutterActor *self)
return self->priv->last_child; return self->priv->last_child;
} }
/* easy way to have properly named fields instead of the dummy ones
* we use in the public structure
*/
typedef struct _RealActorIter
{
ClutterActor *root; /* dummy1 */
ClutterActor *current; /* dummy2 */
gpointer padding_1; /* dummy3 */
gint age; /* dummy4 */
gpointer padding_2; /* dummy5 */
} RealActorIter;
/**
* clutter_actor_iter_init:
* @iter: a #ClutterActorIter
* @root: a #ClutterActor
*
* Initializes a #ClutterActorIter, which can then be used to iterate
* efficiently over a section of the scene graph, and associates it
* with @root.
*
* Modifying the scene graph section that contains @root will invalidate
* the iterator.
*
* |[
* ClutterActorIter iter;
* ClutterActor *child;
*
* clutter_actor_iter_init (&iter, container);
* while (clutter_actor_iter_next (&iter, &child))
* {
* /* do something with child */
* }
* ]|
*
* Since: 1.10
*/
void
clutter_actor_iter_init (ClutterActorIter *iter,
ClutterActor *root)
{
RealActorIter *ri = (RealActorIter *) iter;
g_return_if_fail (iter != NULL);
g_return_if_fail (CLUTTER_IS_ACTOR (root));
ri->root = root;
ri->current = NULL;
ri->age = root->priv->age;
}
/**
* clutter_actor_iter_next:
* @iter: a #ClutterActorIter
* @child: (out): return location for a #ClutterActor
*
* Advances the @iter and retrieves the next child of the root #ClutterActor
* that was used to initialize the #ClutterActorIterator.
*
* If the iterator can advance, this function returns %TRUE and sets the
* @child argument.
*
* If the iterator cannot advance, this function returns %FALSE, and
* the contents of @child are undefined.
*
* Return value: %TRUE if the iterator could advance, and %FALSE otherwise.
*
* Since: 1.10
*/
gboolean
clutter_actor_iter_next (ClutterActorIter *iter,
ClutterActor **child)
{
RealActorIter *ri = (RealActorIter *) iter;
g_return_val_if_fail (iter != NULL, FALSE);
g_return_val_if_fail (ri->root != NULL, FALSE);
#ifndef G_DISABLE_ASSERT
g_return_val_if_fail (ri->age == ri->root->priv->age, FALSE);
#endif
if (ri->current == NULL)
ri->current = ri->root->priv->first_child;
else
ri->current = ri->current->priv->next_sibling;
if (child != NULL)
*child = ri->current;
return ri->current != NULL;
}
/**
* clutter_actor_iter_next:
* @iter: a #ClutterActorIter
* @child: (out): return location for a #ClutterActor
*
* Advances the @iter and retrieves the previous child of the root
* #ClutterActor that was used to initialize the #ClutterActorIterator.
*
* If the iterator can advance, this function returns %TRUE and sets the
* @child argument.
*
* If the iterator cannot advance, this function returns %FALSE, and
* the contents of @child are undefined.
*
* Return value: %TRUE if the iterator could advance, and %FALSE otherwise.
*
* Since: 1.10
*/
gboolean
clutter_actor_iter_prev (ClutterActorIter *iter,
ClutterActor **child)
{
RealActorIter *ri = (RealActorIter *) iter;
g_return_val_if_fail (iter != NULL, FALSE);
g_return_val_if_fail (ri->root != NULL, FALSE);
#ifndef G_DISABLE_ASSERT
g_return_val_if_fail (ri->age == ri->root->priv->age, FALSE);
#endif
if (ri->current == NULL)
ri->current = ri->root->priv->last_child;
else
ri->current = ri->current->priv->prev_sibling;
if (child != NULL)
*child = ri->current;
return ri->current != NULL;
}
/**
* clutter_actor_iter_remove:
* @iter: a #ClutterActorIter
*
* Safely removes the #ClutterActor currently pointer to by the iterator
* from its parent.
*
* This function can only be called after clutter_actor_iter_next() or
* clutter_actor_iter_prev() returned %TRUE, and cannot be called more
* than once for the same actor.
*
* This function will call clutter_actor_remove_child() internally.
*
* Since: 1.10
*/
void
clutter_actor_iter_remove (ClutterActorIter *iter)
{
RealActorIter *ri = (RealActorIter *) iter;
ClutterActor *cur;
g_return_if_fail (iter != NULL);
g_return_if_fail (ri->root != NULL);
#ifndef G_DISABLE_ASSERT
g_return_if_fail (ri->age == ri->root->priv->age);
#endif
g_return_if_fail (ri->current != NULL);
cur = ri->current;
if (cur != NULL)
{
ri->current = cur->priv->prev_sibling;
clutter_actor_remove_child_internal (ri->root, cur,
REMOVE_CHILD_DEFAULT_FLAGS);
ri->age += 1;
}
}

View File

@ -263,6 +263,27 @@ struct _ClutterActorClass
gpointer _padding_dummy[28]; gpointer _padding_dummy[28];
}; };
/**
* ClutterActorIter:
*
* An iterator structure that allows to efficiently iterate over a
* section of the scene graph.
*
* The contents of the <structname>ClutterActorIter</structname> structure
* are private and should only be accessed using the provided API.
*
* Since: 1.10
*/
struct _ClutterActorIter
{
/*< private >*/
gpointer CLUTTER_PRIVATE_FIELD (dummy1);
gpointer CLUTTER_PRIVATE_FIELD (dummy2);
gpointer CLUTTER_PRIVATE_FIELD (dummy3);
gint CLUTTER_PRIVATE_FIELD (dummy4);
gpointer CLUTTER_PRIVATE_FIELD (dummy5);
};
GType clutter_actor_get_type (void) G_GNUC_CONST; GType clutter_actor_get_type (void) G_GNUC_CONST;
ClutterActor * clutter_actor_new (void); ClutterActor * clutter_actor_new (void);
@ -486,6 +507,14 @@ void clutter_actor_set_child_at_index (ClutterActor
ClutterActor *child, ClutterActor *child,
gint index_); gint index_);
void clutter_actor_iter_init (ClutterActorIter *iter,
ClutterActor *root);
gboolean clutter_actor_iter_next (ClutterActorIter *iter,
ClutterActor **child);
gboolean clutter_actor_iter_prev (ClutterActorIter *iter,
ClutterActor **child);
void clutter_actor_iter_remove (ClutterActorIter *iter);
/* Transformations */ /* Transformations */
gboolean clutter_actor_is_rotated (ClutterActor *self); gboolean clutter_actor_is_rotated (ClutterActor *self);
gboolean clutter_actor_is_scaled (ClutterActor *self); gboolean clutter_actor_is_scaled (ClutterActor *self);

View File

@ -48,6 +48,18 @@ G_BEGIN_DECLS
#define CLUTTER_DEPRECATED_FOR(f) G_DEPRECATED_FOR(f) #define CLUTTER_DEPRECATED_FOR(f) G_DEPRECATED_FOR(f)
#endif #endif
/* some structures are meant to be opaque and still be allocated on the stack;
* in order to avoid people poking at their internals, we use this macro to
* ensure that users don't accidentally access a struct private members.
*
* we use the CLUTTER_COMPILATION define to allow us easier access, though.
*/
#ifdef CLUTTER_COMPILATION
#define CLUTTER_PRIVATE_FIELD(x) x
#else
#define CLUTTER_PRIVATE_FIELD(x) clutter_private_ ## x
#endif
#define CLUTTER_TYPE_ACTOR_BOX (clutter_actor_box_get_type ()) #define CLUTTER_TYPE_ACTOR_BOX (clutter_actor_box_get_type ())
#define CLUTTER_TYPE_FOG (clutter_fog_get_type ()) #define CLUTTER_TYPE_FOG (clutter_fog_get_type ())
#define CLUTTER_TYPE_GEOMETRY (clutter_geometry_get_type ()) #define CLUTTER_TYPE_GEOMETRY (clutter_geometry_get_type ())
@ -65,6 +77,7 @@ typedef struct _ClutterChildMeta ClutterChildMeta;
typedef struct _ClutterLayoutMeta ClutterLayoutMeta; typedef struct _ClutterLayoutMeta ClutterLayoutMeta;
typedef struct _ClutterActorMeta ClutterActorMeta; typedef struct _ClutterActorMeta ClutterActorMeta;
typedef struct _ClutterLayoutManager ClutterLayoutManager; typedef struct _ClutterLayoutManager ClutterLayoutManager;
typedef struct _ClutterActorIter ClutterActorIter;
typedef struct _ClutterAlpha ClutterAlpha; typedef struct _ClutterAlpha ClutterAlpha;
typedef struct _ClutterAnimatable ClutterAnimatable; /* dummy */ typedef struct _ClutterAnimatable ClutterAnimatable; /* dummy */

View File

@ -157,6 +157,10 @@ clutter_actor_insert_child_below
clutter_actor_is_in_clone_paint clutter_actor_is_in_clone_paint
clutter_actor_is_rotated clutter_actor_is_rotated
clutter_actor_is_scaled clutter_actor_is_scaled
clutter_actor_iter_init
clutter_actor_iter_next
clutter_actor_iter_prev
clutter_actor_iter_remove
clutter_actor_lower clutter_actor_lower
clutter_actor_lower_bottom clutter_actor_lower_bottom
clutter_actor_map clutter_actor_map

View File

@ -403,6 +403,13 @@ clutter_actor_set_child_at_index
clutter_actor_set_child_below_sibling clutter_actor_set_child_below_sibling
clutter_actor_contains clutter_actor_contains
clutter_actor_get_stage clutter_actor_get_stage
ClutterActorIter
clutter_actor_iter_init
clutter_actor_iter_next
clutter_actor_iter_prev
clutter_actor_iter_remove
<SUBSECTION>
clutter_actor_push_internal clutter_actor_push_internal
clutter_actor_pop_internal clutter_actor_pop_internal
clutter_actor_set_parent clutter_actor_set_parent

View File

@ -62,6 +62,7 @@ units_sources += \
test-actor-graph.c \ test-actor-graph.c \
test-actor-destroy.c \ test-actor-destroy.c \
test-actor-invariants.c \ test-actor-invariants.c \
test-actor-iter.c \
test-actor-layout.c \ test-actor-layout.c \
test-actor-size.c \ test-actor-size.c \
test-anchors.c \ test-anchors.c \

View File

@ -0,0 +1,129 @@
#include <glib.h>
#include <clutter/clutter.h>
#include "test-conform-common.h"
void
actor_iter_traverse_children (TestConformSimpleFixture *fixture G_GNUC_UNUSED,
gconstpointer dummy G_GNUC_UNUSED)
{
ClutterActorIter iter;
ClutterActor *actor;
ClutterActor *child;
int i, n_actors;
actor = clutter_actor_new ();
clutter_actor_set_name (actor, "root");
g_object_ref_sink (actor);
n_actors = g_random_int_range (10, 50);
for (i = 0; i < n_actors; i++)
{
char *name;
name = g_strdup_printf ("actor%d", i);
child = clutter_actor_new ();
clutter_actor_set_name (child, name);
clutter_actor_add_child (actor, child);
g_free (name);
}
g_assert_cmpint (clutter_actor_get_n_children (actor), ==, n_actors);
i = 0;
clutter_actor_iter_init (&iter, actor);
while (clutter_actor_iter_next (&iter, &child))
{
g_assert (CLUTTER_IS_ACTOR (child));
g_assert (clutter_actor_get_parent (child) == actor);
if (g_test_verbose ())
g_print ("actor %d = '%s'\n", i, clutter_actor_get_name (child));
if (i == 0)
g_assert (child == clutter_actor_get_first_child (actor));
if (i == (n_actors - 1))
g_assert (child == clutter_actor_get_last_child (actor));
i += 1;
}
g_assert_cmpint (i, ==, n_actors);
i = 0;
clutter_actor_iter_init (&iter, actor);
while (clutter_actor_iter_prev (&iter, &child))
{
g_assert (CLUTTER_IS_ACTOR (child));
g_assert (clutter_actor_get_parent (child) == actor);
if (g_test_verbose ())
g_print ("actor %d = '%s'\n", i, clutter_actor_get_name (child));
if (i == 0)
g_assert (child == clutter_actor_get_last_child (actor));
if (i == (n_actors - 1))
g_assert (child == clutter_actor_get_first_child (actor));
i += 1;
}
g_object_unref (actor);
}
void
actor_iter_traverse_remove (TestConformSimpleFixture *fixture G_GNUC_UNUSED,
gconstpointer dummy G_GNUC_UNUSED)
{
ClutterActorIter iter;
ClutterActor *actor;
ClutterActor *child;
int i, n_actors;
actor = clutter_actor_new ();
clutter_actor_set_name (actor, "root");
g_object_ref_sink (actor);
n_actors = g_random_int_range (10, 50);
for (i = 0; i < n_actors; i++)
{
char *name;
name = g_strdup_printf ("actor%d", i);
child = clutter_actor_new ();
clutter_actor_set_name (child, name);
clutter_actor_add_child (actor, child);
g_free (name);
}
g_assert_cmpint (clutter_actor_get_n_children (actor), ==, n_actors);
i = 0;
clutter_actor_iter_init (&iter, actor);
while (clutter_actor_iter_next (&iter, &child))
{
g_assert (CLUTTER_IS_ACTOR (child));
g_assert (clutter_actor_get_parent (child) == actor);
if (g_test_verbose ())
g_print ("actor %d = '%s'\n", i, clutter_actor_get_name (child));
if (i == 0)
g_assert (child == clutter_actor_get_first_child (actor));
if (i == (n_actors - 1))
g_assert (child == clutter_actor_get_last_child (actor));
clutter_actor_iter_remove (&iter);
i += 1;
}
g_assert_cmpint (i, ==, n_actors);
g_assert_cmpint (0, ==, clutter_actor_get_n_children (actor));
}

View File

@ -145,20 +145,23 @@ main (int argc, char **argv)
TEST_CONFORM_SIMPLE ("/actor", test_offscreen_redirect); TEST_CONFORM_SIMPLE ("/actor", test_offscreen_redirect);
TEST_CONFORM_SIMPLE ("/actor", test_shader_effect); TEST_CONFORM_SIMPLE ("/actor", test_shader_effect);
TEST_CONFORM_SIMPLE ("/invariants", test_initial_state); TEST_CONFORM_SIMPLE ("/actor/iter", actor_iter_traverse_children);
TEST_CONFORM_SIMPLE ("/invariants", test_shown_not_parented); TEST_CONFORM_SIMPLE ("/actor/iter", actor_iter_traverse_remove);
TEST_CONFORM_SIMPLE ("/invariants", test_realized);
TEST_CONFORM_SIMPLE ("/invariants", test_realize_not_recursive);
TEST_CONFORM_SIMPLE ("/invariants", test_map_recursive);
TEST_CONFORM_SIMPLE ("/invariants", test_mapped);
TEST_CONFORM_SIMPLE ("/invariants", test_show_on_set_parent);
TEST_CONFORM_SIMPLE ("/invariants", test_clone_no_map);
TEST_CONFORM_SIMPLE ("/invariants", test_contains);
TEST_CONFORM_SIMPLE ("/invariants", default_stage);
TEST_CONFORM_SIMPLE ("/opacity", test_label_opacity); TEST_CONFORM_SIMPLE ("/actor/invariants", test_initial_state);
TEST_CONFORM_SIMPLE ("/opacity", test_rectangle_opacity); TEST_CONFORM_SIMPLE ("/actor/invariants", test_shown_not_parented);
TEST_CONFORM_SIMPLE ("/opacity", test_paint_opacity); TEST_CONFORM_SIMPLE ("/actor/invariants", test_realized);
TEST_CONFORM_SIMPLE ("/actor/invariants", test_realize_not_recursive);
TEST_CONFORM_SIMPLE ("/actor/invariants", test_map_recursive);
TEST_CONFORM_SIMPLE ("/actor/invariants", test_mapped);
TEST_CONFORM_SIMPLE ("/actor/invariants", test_show_on_set_parent);
TEST_CONFORM_SIMPLE ("/actor/invariants", test_clone_no_map);
TEST_CONFORM_SIMPLE ("/actor/invariants", test_contains);
TEST_CONFORM_SIMPLE ("/actor/invariants", default_stage);
TEST_CONFORM_SIMPLE ("/actor/opacity", test_label_opacity);
TEST_CONFORM_SIMPLE ("/actor/opacity", test_rectangle_opacity);
TEST_CONFORM_SIMPLE ("/actor/opacity", test_paint_opacity);
TEST_CONFORM_SIMPLE ("/text", text_utf8_validation); TEST_CONFORM_SIMPLE ("/text", text_utf8_validation);
TEST_CONFORM_SIMPLE ("/text", text_set_empty); TEST_CONFORM_SIMPLE ("/text", text_set_empty);