actor: make _clutter_actor_traverse more flexible
authorRobert Bragg <robert@linux.intel.com>
Wed, 20 Oct 2010 14:40:30 +0000 (15:40 +0100)
committerChris Lord <chris@linux.intel.com>
Wed, 24 Nov 2010 16:51:59 +0000 (16:51 +0000)
This makes it possible to choose the traversal order; either depth first
or breadth first and when visiting actors in a depth first order there
is now a callback called before children are traversed and one called
after. Some tasks such as unrealizing actors need to explicitly control
the traversal order to maintain the invariable that all children of an
actor are unrealized before we actually mark the parent as unrealized.

The callbacks are now passed the relative depth in the graph of the
actor being visited and instead of only being able to return a boolean
to bail out of further traversal it can now do one of: continue,
skip_children or break. To implement something like unrealize it's
desirable to skip children that you find have already been unrealized.

clutter/clutter-actor-private.h
clutter/clutter-actor.c

index 7162346..27a4b13 100644 (file)
@@ -44,15 +44,55 @@ typedef enum
 } ClutterRedrawFlags;
 
 /* ClutterActorTraverseFlags:
+ * CLUTTER_ACTOR_TRAVERSE_DEPTH_FIRST: Traverse the graph in
+ *   a depth first order.
+ * CLUTTER_ACTOR_TRAVERSE_BREADTH_FIRST: Traverse the graph in a
+ *   breadth first order.
  *
  * Controls some options for how clutter_actor_traverse() iterates
  * through the graph.
  */
 typedef enum _ClutterActorTraverseFlags
 {
-  CLUTTER_ACTOR_TRAVERSE_PLACE_HOLDER  = 1L<<0
+  CLUTTER_ACTOR_TRAVERSE_DEPTH_FIRST   = 1L<<0,
+  CLUTTER_ACTOR_TRAVERSE_BREADTH_FIRST = 1L<<1
 } ClutterActorTraverseFlags;
 
+/**
+ * ClutterActorTraverseVisitFlags:
+ * CLUTTER_ACTOR_TRAVERSE_VISIT_CONTINUE: Continue traversing as
+ *   normal
+ * CLUTTER_ACTOR_TRAVERSE_VISIT_SKIP_CHILDREN: Don't traverse the
+ *   children of the last visited actor. (Not applicable when using
+ *   CLUTTER_ACTOR_TRAVERSE_DEPTH_FIRST_POST_ORDER since the children
+ *   are visited before having an opportunity to bail out)
+ * CLUTTER_ACTOR_TRAVERSE_VISIT_BREAK: Immediately bail out without
+ *   visiting any more actors.
+ *
+ * Each time an actor is visited during a scenegraph traversal the
+ * ClutterTraverseCallback can return a set of flags that may affect
+ * the continuing traversal. It may stop traversal completely, just
+ * skip over children for the current actor or continue as normal.
+ */
+typedef enum _ClutterActorTraverseVisitFlags
+{
+  CLUTTER_ACTOR_TRAVERSE_VISIT_CONTINUE       = 1L<<0,
+  CLUTTER_ACTOR_TRAVERSE_VISIT_SKIP_CHILDREN  = 1L<<1,
+  CLUTTER_ACTOR_TRAVERSE_VISIT_BREAK          = 1L<<2
+} ClutterActorTraverseVisitFlags;
+
+/**
+ * ClutterTraverseCallback:
+ *
+ * The callback prototype used with clutter_actor_traverse. The
+ * returned flags can be used to affect the continuing traversal
+ * either by continuing as normal, skipping over children of an
+ * actor or bailing out completely.
+ */
+typedef ClutterActorTraverseVisitFlags (*ClutterTraverseCallback) (ClutterActor *actor,
+                                                                   int depth,
+                                                                   void *user_data);
+
 /* ClutterForeachCallback:
  * @actor: The actor being iterated
  * @user_data: The private data specified when starting the iteration
@@ -72,9 +112,10 @@ gint          _clutter_actor_get_n_children             (ClutterActor *self);
 gboolean      _clutter_actor_foreach_child              (ClutterActor *self,
                                                          ClutterForeachCallback callback,
                                                          void *user_data);
-gboolean      _clutter_actor_traverse                   (ClutterActor *actor,
+void          _clutter_actor_traverse                   (ClutterActor *actor,
                                                          ClutterActorTraverseFlags flags,
-                                                         ClutterForeachCallback callback,
+                                                         ClutterTraverseCallback before_children_callback,
+                                                         ClutterTraverseCallback after_children_callback,
                                                          void *user_data);
 ClutterActor *_clutter_actor_get_stage_internal         (ClutterActor *actor);
 
index 4bf713b..39a157b 100644 (file)
@@ -7664,8 +7664,9 @@ clutter_actor_get_paint_visibility (ClutterActor *actor)
   return CLUTTER_ACTOR_IS_MAPPED (actor);
 }
 
-static gboolean
+static ClutterActorTraverseVisitFlags
 invalidate_queue_redraw_entry (ClutterActor *self,
+                               int           depth,
                                gpointer      user_data)
 {
   ClutterActorPrivate *priv = self->priv;
@@ -7676,7 +7677,7 @@ invalidate_queue_redraw_entry (ClutterActor *self,
       priv->queue_redraw_entry = NULL;
     }
 
-  return TRUE;
+  return CLUTTER_ACTOR_TRAVERSE_VISIT_CONTINUE;
 }
 
 /**
@@ -7712,6 +7713,7 @@ clutter_actor_unparent (ClutterActor *self)
   _clutter_actor_traverse (self,
                            0,
                            invalidate_queue_redraw_entry,
+                           NULL,
                            NULL);
 
   was_mapped = CLUTTER_ACTOR_IS_MAPPED (self);
@@ -11840,42 +11842,132 @@ _clutter_actor_foreach_child (ClutterActor *self,
   return cont;
 }
 
+/* For debugging purposes this gives us a simple way to print out
+ * the scenegraph e.g in gdb using:
+ * [|
+ *   _clutter_actor_traverse (clutter_stage_get_default (),
+ *                            0,
+ *                            _clutter_debug_print_actor_cb,
+ *                            NULL,
+ *                            NULL);
+ * |]
+ */
+ClutterActorTraverseVisitFlags
+_clutter_debug_print_actor_cb (ClutterActor *actor,
+                               int depth,
+                               void *user_data)
+{
+  g_print ("%*s%s:%p\n", depth * 2, "", G_OBJECT_TYPE_NAME (actor), actor);
+  return CLUTTER_ACTOR_TRAVERSE_VISIT_CONTINUE;
+}
+
+static void
+_clutter_actor_traverse_breadth (ClutterActor           *actor,
+                                 ClutterTraverseCallback callback,
+                                 gpointer                user_data)
+{
+  GQueue *queue = g_queue_new ();
+  ClutterActor dummy;
+  int current_depth = 0;
+
+  g_queue_push_tail (queue, actor);
+  g_queue_push_tail (queue, &dummy); /* use to delimit depth changes */
+
+  while ((actor = g_queue_pop_head (queue)))
+    {
+      ClutterActorTraverseVisitFlags flags;
+
+      if (actor == &dummy)
+        {
+          current_depth++;
+          g_queue_push_tail (queue, &dummy);
+          continue;
+        }
+
+      flags = callback (actor, current_depth, user_data);
+      if (flags & CLUTTER_ACTOR_TRAVERSE_VISIT_BREAK)
+        break;
+      else if (!(flags & CLUTTER_ACTOR_TRAVERSE_VISIT_SKIP_CHILDREN))
+        {
+          GList *l;
+          for (l = actor->priv->children; l; l = l->next)
+            g_queue_push_tail (queue, l->data);
+        }
+    }
+
+  g_queue_free (queue);
+}
+
+static ClutterActorTraverseVisitFlags
+_clutter_actor_traverse_depth (ClutterActor           *actor,
+                               ClutterTraverseCallback before_children_callback,
+                               ClutterTraverseCallback after_children_callback,
+                               int                     current_depth,
+                               gpointer                user_data)
+{
+  ClutterActorTraverseVisitFlags flags;
+
+  flags = before_children_callback (actor, current_depth, user_data);
+  if (flags & CLUTTER_ACTOR_TRAVERSE_VISIT_BREAK)
+    return CLUTTER_ACTOR_TRAVERSE_VISIT_BREAK;
+
+  if (!(flags & CLUTTER_ACTOR_TRAVERSE_VISIT_SKIP_CHILDREN))
+    {
+      GList *l;
+      for (l = actor->priv->children; l; l = l->next)
+        {
+          flags = _clutter_actor_traverse_depth (l->data,
+                                                 before_children_callback,
+                                                 after_children_callback,
+                                                 current_depth + 1,
+                                                 user_data);
+          if (flags & CLUTTER_ACTOR_TRAVERSE_VISIT_BREAK)
+            return CLUTTER_ACTOR_TRAVERSE_VISIT_BREAK;
+        }
+    }
+
+  if (after_children_callback)
+    return after_children_callback (actor, current_depth, user_data);
+  else
+    return CLUTTER_ACTOR_TRAVERSE_VISIT_CONTINUE;
+}
+
 /* _clutter_actor_traverse:
  * @actor: The actor to start traversing the graph from
  * @flags: These flags may affect how the traversal is done
- * @callback: The function to call for each actor traversed
- * @user_data: The private data to pass to the @callback
+ * @before_children_callback: A function to call before visiting the
+ *   children of the current actor.
+ * @after_children_callback: A function to call after visiting the
+ *   children of the current actor. (Ignored if
+ *   %CLUTTER_ACTOR_TRAVERSE_BREADTH_FIRST is passed to @flags.)
+ * @user_data: The private data to pass to the callbacks
  *
  * Traverses the scenegraph starting at the specified @actor and
  * descending through all its children and its children's children.
- * For each actor traversed @callback is called with the specified
- * @user_data.
+ * For each actor traversed @before_children_callback and
+ * @after_children_callback are called with the specified
+ * @user_data, before and after visiting that actor's children.
  *
- * If @callback ever returns %FALSE then no more actors will be
- * traversed.
- *
- * Return value: %TRUE if @actor and all its descendants were
- *   traversed or %FALSE if the @callback returned %FALSE to stop
- *   traversal early.
+ * The callbacks can return flags that affect the ongoing traversal
+ * such as by skipping over an actors children or bailing out of
+ * any further traversing.
  */
-gboolean
+void
 _clutter_actor_traverse (ClutterActor              *actor,
                          ClutterActorTraverseFlags  flags,
-                         ClutterForeachCallback     callback,
+                         ClutterTraverseCallback    before_children_callback,
+                         ClutterTraverseCallback    after_children_callback,
                          gpointer                   user_data)
 {
-  ClutterActorPrivate *priv;
-  GList *l;
-  gboolean cont;
-
-  if (!callback (actor, user_data))
-    return FALSE;
-
-  priv = actor->priv;
-
-  for (cont = TRUE, l = priv->children; cont && l; l = l->next)
-    cont = _clutter_actor_traverse (l->data, flags, callback, user_data);
-
-  return cont;
+  if (flags & CLUTTER_ACTOR_TRAVERSE_BREADTH_FIRST)
+    _clutter_actor_traverse_breadth (actor,
+                                     before_children_callback,
+                                     user_data);
+  else /* DEPTH_FIRST */
+    _clutter_actor_traverse_depth (actor,
+                                   before_children_callback,
+                                   after_children_callback,
+                                   0, /* start depth */
+                                   user_data);
 }