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);