diff --git a/clutter/clutter-actor.c b/clutter/clutter-actor.c
index b11e5625e..8999e4e26 100644
--- a/clutter/clutter-actor.c
+++ b/clutter/clutter-actor.c
@@ -395,6 +395,14 @@ struct _ClutterActorPrivate
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 */
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->age += 1;
+
/* clutter_actor_reparent() will emit ::parent-set for us */
if (emit_parent_set && !CLUTTER_ACTOR_IN_REPARENT (child))
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->age += 1;
+
/* if push_internal() has been called then we automatically set
* the flag on the actor
*/
@@ -15770,3 +15782,177 @@ clutter_actor_get_last_child (ClutterActor *self)
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;
+ }
+}
diff --git a/clutter/clutter-actor.h b/clutter/clutter-actor.h
index c7e021ee6..279259acf 100644
--- a/clutter/clutter-actor.h
+++ b/clutter/clutter-actor.h
@@ -263,6 +263,27 @@ struct _ClutterActorClass
gpointer _padding_dummy[28];
};
+/**
+ * ClutterActorIter:
+ *
+ * An iterator structure that allows to efficiently iterate over a
+ * section of the scene graph.
+ *
+ * The contents of the ClutterActorIter 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;
ClutterActor * clutter_actor_new (void);
@@ -486,6 +507,14 @@ void clutter_actor_set_child_at_index (ClutterActor
ClutterActor *child,
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 */
gboolean clutter_actor_is_rotated (ClutterActor *self);
gboolean clutter_actor_is_scaled (ClutterActor *self);
diff --git a/clutter/clutter-types.h b/clutter/clutter-types.h
index f9c109ea7..2136874ee 100644
--- a/clutter/clutter-types.h
+++ b/clutter/clutter-types.h
@@ -48,6 +48,18 @@ G_BEGIN_DECLS
#define CLUTTER_DEPRECATED_FOR(f) G_DEPRECATED_FOR(f)
#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_FOG (clutter_fog_get_type ())
#define CLUTTER_TYPE_GEOMETRY (clutter_geometry_get_type ())
@@ -65,6 +77,7 @@ typedef struct _ClutterChildMeta ClutterChildMeta;
typedef struct _ClutterLayoutMeta ClutterLayoutMeta;
typedef struct _ClutterActorMeta ClutterActorMeta;
typedef struct _ClutterLayoutManager ClutterLayoutManager;
+typedef struct _ClutterActorIter ClutterActorIter;
typedef struct _ClutterAlpha ClutterAlpha;
typedef struct _ClutterAnimatable ClutterAnimatable; /* dummy */
diff --git a/clutter/clutter.symbols b/clutter/clutter.symbols
index 294200f4d..98510fd9a 100644
--- a/clutter/clutter.symbols
+++ b/clutter/clutter.symbols
@@ -157,6 +157,10 @@ clutter_actor_insert_child_below
clutter_actor_is_in_clone_paint
clutter_actor_is_rotated
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_bottom
clutter_actor_map
diff --git a/doc/reference/clutter/clutter-sections.txt b/doc/reference/clutter/clutter-sections.txt
index 789f945dd..55d7a9a92 100644
--- a/doc/reference/clutter/clutter-sections.txt
+++ b/doc/reference/clutter/clutter-sections.txt
@@ -403,6 +403,13 @@ clutter_actor_set_child_at_index
clutter_actor_set_child_below_sibling
clutter_actor_contains
clutter_actor_get_stage
+ClutterActorIter
+clutter_actor_iter_init
+clutter_actor_iter_next
+clutter_actor_iter_prev
+clutter_actor_iter_remove
+
+
clutter_actor_push_internal
clutter_actor_pop_internal
clutter_actor_set_parent
diff --git a/tests/conform/Makefile.am b/tests/conform/Makefile.am
index ccc07aca0..59d5e3e34 100644
--- a/tests/conform/Makefile.am
+++ b/tests/conform/Makefile.am
@@ -62,6 +62,7 @@ units_sources += \
test-actor-graph.c \
test-actor-destroy.c \
test-actor-invariants.c \
+ test-actor-iter.c \
test-actor-layout.c \
test-actor-size.c \
test-anchors.c \
diff --git a/tests/conform/test-actor-iter.c b/tests/conform/test-actor-iter.c
new file mode 100644
index 000000000..fdfb0d5bb
--- /dev/null
+++ b/tests/conform/test-actor-iter.c
@@ -0,0 +1,129 @@
+#include
+#include
+#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));
+}
diff --git a/tests/conform/test-conform-main.c b/tests/conform/test-conform-main.c
index a6431337a..1a6f608fc 100644
--- a/tests/conform/test-conform-main.c
+++ b/tests/conform/test-conform-main.c
@@ -145,20 +145,23 @@ main (int argc, char **argv)
TEST_CONFORM_SIMPLE ("/actor", test_offscreen_redirect);
TEST_CONFORM_SIMPLE ("/actor", test_shader_effect);
- TEST_CONFORM_SIMPLE ("/invariants", test_initial_state);
- TEST_CONFORM_SIMPLE ("/invariants", test_shown_not_parented);
- 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 ("/actor/iter", actor_iter_traverse_children);
+ TEST_CONFORM_SIMPLE ("/actor/iter", actor_iter_traverse_remove);
- TEST_CONFORM_SIMPLE ("/opacity", test_label_opacity);
- TEST_CONFORM_SIMPLE ("/opacity", test_rectangle_opacity);
- TEST_CONFORM_SIMPLE ("/opacity", test_paint_opacity);
+ TEST_CONFORM_SIMPLE ("/actor/invariants", test_initial_state);
+ TEST_CONFORM_SIMPLE ("/actor/invariants", test_shown_not_parented);
+ 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_set_empty);