[mutter/gbsneto/graphene-ray: 1/6] Introduce ClutterPickStack




commit f411834d42a11172ac8fd090051ed174f437e09f
Author: Georges Basile Stavracas Neto <georges stavracas gmail com>
Date:   Fri Oct 16 19:13:12 2020 -0300

    Introduce ClutterPickStack
    
    ClutterPickStack is a new boxed type that stores the vertices
    and clip rectangles. It is meant to be a byproduct of picking,
    and takes over most of what ClutterStage currently does.
    
    It introduces a 'seal' system, inspired by MetaKmsUpdate. After
    the pick operation is done, and the rectangles are collected,
    the pick stack is sealed, and is not allowed to be externally
    modified anymore. Internally, it still can invalidate pick
    records when an actor is destroyed.
    
    For now, it handles both the clip rectangles, and the matrix
    stack, separatedly. Future commits will rearrange this.
    
    https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1509

 clutter/clutter/clutter-actor.c                |  30 +-
 clutter/clutter/clutter-pick-context-private.h |   4 +
 clutter/clutter/clutter-pick-context.c         | 101 +++++++
 clutter/clutter/clutter-pick-context.h         |  23 ++
 clutter/clutter/clutter-pick-stack-private.h   |  65 +++++
 clutter/clutter/clutter-pick-stack.c           | 386 +++++++++++++++++++++++++
 clutter/clutter/clutter-stage-private.h        |   9 -
 clutter/clutter/clutter-stage.c                | 285 +-----------------
 clutter/clutter/meson.build                    |   1 +
 9 files changed, 595 insertions(+), 309 deletions(-)
---
diff --git a/clutter/clutter/clutter-actor.c b/clutter/clutter/clutter-actor.c
index 4a92d6ffd2..867b54c477 100644
--- a/clutter/clutter/clutter-actor.c
+++ b/clutter/clutter/clutter-actor.c
@@ -1252,8 +1252,6 @@ _clutter_actor_transform_local_box_to_stage (ClutterActor          *self,
 {
   ClutterActor *stage_actor = CLUTTER_ACTOR (stage);
   ClutterActorPrivate *stage_priv = stage_actor->priv;
-  CoglFramebuffer *fb =
-   clutter_pick_context_get_framebuffer (pick_context);
   graphene_matrix_t modelview, transform_to_stage;
   int v;
 
@@ -1261,7 +1259,7 @@ _clutter_actor_transform_local_box_to_stage (ClutterActor          *self,
 
   if (!stage_priv->has_inverse_transform)
     return FALSE;
-  cogl_framebuffer_get_modelview_matrix (fb, &modelview);
+  clutter_pick_context_get_transform (pick_context, &modelview);
   graphene_matrix_multiply (&modelview,
                             &stage_priv->inverse_transform,
                             &transform_to_stage);
@@ -1326,7 +1324,7 @@ clutter_actor_pick_box (ClutterActor          *self,
 
   if (_clutter_actor_transform_local_box_to_stage (self, stage, pick_context,
                                                    box, vertices))
-    clutter_stage_log_pick (stage, vertices, self);
+    clutter_pick_context_log_pick (pick_context, vertices, self);
 }
 
 static gboolean
@@ -1343,19 +1341,10 @@ _clutter_actor_push_pick_clip (ClutterActor          *self,
                                                     clip, vertices))
     return FALSE;
 
-  clutter_stage_push_pick_clip (stage, vertices);
+  clutter_pick_context_push_clip (pick_context, vertices);
   return TRUE;
 }
 
-static void
-_clutter_actor_pop_pick_clip (ClutterActor *self)
-{
-  ClutterActor *stage;
-
-  stage = _clutter_actor_get_stage_internal (self);
-  clutter_stage_pop_pick_clip (CLUTTER_STAGE (stage));
-}
-
 static void
 clutter_actor_set_mapped (ClutterActor *self,
                           gboolean      mapped)
@@ -4021,7 +4010,6 @@ clutter_actor_pick (ClutterActor       *actor,
                     ClutterPickContext *pick_context)
 {
   ClutterActorPrivate *priv;
-  CoglFramebuffer *framebuffer;
   ClutterActorBox clip;
   gboolean clip_set = FALSE;
 
@@ -4039,16 +4027,13 @@ clutter_actor_pick (ClutterActor       *actor,
   /* mark that we are in the paint process */
   CLUTTER_SET_PRIVATE_FLAGS (actor, CLUTTER_IN_PICK);
 
-  framebuffer = clutter_pick_context_get_framebuffer (pick_context);
-  cogl_framebuffer_push_matrix (framebuffer);
-
   if (priv->enable_model_view_transform)
     {
       graphene_matrix_t matrix;
 
-      cogl_framebuffer_get_modelview_matrix (framebuffer, &matrix);
+      graphene_matrix_init_identity (&matrix);
       _clutter_actor_apply_modelview_transform (actor, &matrix);
-      cogl_framebuffer_set_modelview_matrix (framebuffer, &matrix);
+      clutter_pick_context_push_transform (pick_context, &matrix);
     }
 
   if (priv->has_clip)
@@ -4081,9 +4066,10 @@ clutter_actor_pick (ClutterActor       *actor,
   clutter_actor_continue_pick (actor, pick_context);
 
   if (clip_set)
-    _clutter_actor_pop_pick_clip (actor);
+    clutter_pick_context_pop_clip (pick_context);
 
-  cogl_framebuffer_pop_matrix (framebuffer);
+  if (priv->enable_model_view_transform)
+    clutter_pick_context_pop_transform (pick_context);
 
   /* paint sequence complete */
   CLUTTER_UNSET_PRIVATE_FLAGS (actor, CLUTTER_IN_PICK);
diff --git a/clutter/clutter/clutter-pick-context-private.h b/clutter/clutter/clutter-pick-context-private.h
index 7e4422eddf..cfbfce6eb4 100644
--- a/clutter/clutter/clutter-pick-context-private.h
+++ b/clutter/clutter/clutter-pick-context-private.h
@@ -19,8 +19,12 @@
 #define CLUTTER_PICK_CONTEXT_PRIVATE_H
 
 #include "clutter-pick-context.h"
+#include "clutter-pick-stack-private.h"
 
 ClutterPickContext * clutter_pick_context_new_for_view (ClutterStageView *view,
                                                         ClutterPickMode   mode);
 
+ClutterPickStack *
+clutter_pick_context_steal_stack (ClutterPickContext *pick_context);
+
 #endif /* CLUTTER_PICK_CONTEXT_PRIVATE_H */
diff --git a/clutter/clutter/clutter-pick-context.c b/clutter/clutter/clutter-pick-context.c
index 6209e58cee..29027284aa 100644
--- a/clutter/clutter/clutter-pick-context.c
+++ b/clutter/clutter/clutter-pick-context.c
@@ -25,6 +25,7 @@ struct _ClutterPickContext
 
   ClutterPickMode mode;
   CoglFramebuffer *framebuffer;
+  ClutterPickStack *pick_stack;
 };
 
 G_DEFINE_BOXED_TYPE (ClutterPickContext, clutter_pick_context,
@@ -36,6 +37,7 @@ clutter_pick_context_new_for_view (ClutterStageView *view,
                                    ClutterPickMode   mode)
 {
   ClutterPickContext *pick_context;
+  CoglContext *context;
 
   pick_context = g_new0 (ClutterPickContext, 1);
   g_ref_count_init (&pick_context->ref_count);
@@ -43,6 +45,9 @@ clutter_pick_context_new_for_view (ClutterStageView *view,
   pick_context->framebuffer =
     g_object_ref (clutter_stage_view_get_framebuffer (view));
 
+  context = cogl_framebuffer_get_context (pick_context->framebuffer);
+  pick_context->pick_stack = clutter_pick_stack_new (context);
+
   return pick_context;
 }
 
@@ -56,6 +61,7 @@ clutter_pick_context_ref (ClutterPickContext *pick_context)
 static void
 clutter_pick_context_dispose (ClutterPickContext *pick_context)
 {
+  g_clear_pointer (&pick_context->pick_stack, clutter_pick_stack_unref);
   g_clear_object (&pick_context->framebuffer);
 }
 
@@ -93,3 +99,98 @@ clutter_pick_context_get_mode (ClutterPickContext *pick_context)
 {
   return pick_context->mode;
 }
+
+ClutterPickStack *
+clutter_pick_context_steal_stack (ClutterPickContext *pick_context)
+{
+  clutter_pick_stack_seal (pick_context->pick_stack);
+  return g_steal_pointer (&pick_context->pick_stack);
+}
+
+/**
+ * clutter_pick_context_log_pick:
+ * @pick_context: a #ClutterPickContext
+ * @vertices: (array fixed-size=4): array of #graphene_point_t
+ * @actor: a #ClutterActor
+ *
+ * Logs a pick rectangle into the pick stack.
+ */
+void
+clutter_pick_context_log_pick (ClutterPickContext     *pick_context,
+                               const graphene_point_t  vertices[4],
+                               ClutterActor           *actor)
+{
+  clutter_pick_stack_log_pick (pick_context->pick_stack, vertices, actor);
+}
+
+/**
+ * clutter_pick_context_push_clip:
+ * @pick_context: a #ClutterPickContext
+ * @vertices: (array fixed-size=4): array of #graphene_point_t
+ *
+ * Pushes a clip rectangle defined by @vertices into the pick stack.
+ * Pop with clutter_pick_context_pop_clip() when done.
+ */
+void
+clutter_pick_context_push_clip (ClutterPickContext     *pick_context,
+                                const graphene_point_t  vertices[4])
+{
+  clutter_pick_stack_push_clip (pick_context->pick_stack, vertices);
+}
+
+/**
+ * clutter_pick_context_pop_clip:
+ * @pick_context: a #ClutterPickContext
+ *
+ * Pops the current clip rectangle from the clip stack. It is a programming
+ * error to call this without a corresponding clutter_pick_context_push_clip()
+ * call first.
+ */
+void
+clutter_pick_context_pop_clip (ClutterPickContext *pick_context)
+{
+  clutter_pick_stack_pop_clip (pick_context->pick_stack);
+}
+
+/**
+ * clutter_pick_context_push_transform:
+ * @pick_context: a #ClutterPickContext
+ * @transform: a #graphene_matrix_t
+ *
+ * Pushes @transform into the pick stack. Pop with
+ * clutter_pick_context_pop_transform() when done.
+ */
+void
+clutter_pick_context_push_transform (ClutterPickContext      *pick_context,
+                                     const graphene_matrix_t *transform)
+{
+  clutter_pick_stack_push_transform (pick_context->pick_stack, transform);
+}
+
+/**
+ * clutter_pick_context_get_transform:
+ * @pick_context: a #ClutterPickContext
+ * @out_matrix: (out): a #graphene_matrix_t
+ *
+ * Retrieves the current transform of the pick stack.
+ */
+void
+clutter_pick_context_get_transform (ClutterPickContext *pick_context,
+                                    graphene_matrix_t  *out_transform)
+{
+  clutter_pick_stack_get_transform (pick_context->pick_stack, out_transform);
+}
+
+/**
+ * clutter_pick_context_pop_transform:
+ * @pick_context: a #ClutterPickContext
+ *
+ * Pops the current transform from the clip stack. It is a programming error
+ * to call this without a corresponding clutter_pick_context_push_transform()
+ * call first.
+ */
+void
+clutter_pick_context_pop_transform (ClutterPickContext *pick_context)
+{
+  clutter_pick_stack_pop_transform (pick_context->pick_stack);
+}
diff --git a/clutter/clutter/clutter-pick-context.h b/clutter/clutter/clutter-pick-context.h
index d420d0a57a..4afa25af71 100644
--- a/clutter/clutter/clutter-pick-context.h
+++ b/clutter/clutter/clutter-pick-context.h
@@ -49,4 +49,27 @@ CoglFramebuffer * clutter_pick_context_get_framebuffer (ClutterPickContext *pick
 CLUTTER_EXPORT
 ClutterPickMode clutter_pick_context_get_mode (ClutterPickContext *pick_context);
 
+CLUTTER_EXPORT
+void clutter_pick_context_log_pick (ClutterPickContext     *pick_context,
+                                    const graphene_point_t  vertices[4],
+                                    ClutterActor           *actor);
+
+CLUTTER_EXPORT
+void clutter_pick_context_push_clip (ClutterPickContext     *pick_context,
+                                     const graphene_point_t  vertices[4]);
+
+CLUTTER_EXPORT
+void clutter_pick_context_pop_clip (ClutterPickContext *pick_context);
+
+CLUTTER_EXPORT
+void clutter_pick_context_push_transform (ClutterPickContext      *pick_context,
+                                          const graphene_matrix_t *transform);
+
+CLUTTER_EXPORT
+void clutter_pick_context_get_transform (ClutterPickContext *pick_context,
+                                         graphene_matrix_t  *out_matrix);
+
+CLUTTER_EXPORT
+void clutter_pick_context_pop_transform (ClutterPickContext *pick_context);
+
 #endif /* CLUTTER_PICK_CONTEXT_H */
diff --git a/clutter/clutter/clutter-pick-stack-private.h b/clutter/clutter/clutter-pick-stack-private.h
new file mode 100644
index 0000000000..1be943aaea
--- /dev/null
+++ b/clutter/clutter/clutter-pick-stack-private.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2020 Endless OS Foundation, LLC
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef CLUTTER_PICK_STACK_PRIVATE_H
+#define CLUTTER_PICK_STACK_PRIVATE_H
+
+#include <glib-object.h>
+
+#include "clutter-macros.h"
+#include "clutter-stage-view.h"
+
+G_BEGIN_DECLS
+
+#define CLUTTER_TYPE_PICK_STACK (clutter_pick_stack_get_type ())
+
+typedef struct _ClutterPickStack ClutterPickStack;
+
+GType clutter_pick_stack_get_type (void) G_GNUC_CONST;
+
+ClutterPickStack * clutter_pick_stack_new (CoglContext *context);
+
+ClutterPickStack * clutter_pick_stack_ref (ClutterPickStack *pick_stack);
+
+void clutter_pick_stack_unref (ClutterPickStack *pick_stack);
+
+void clutter_pick_stack_seal (ClutterPickStack *pick_stack);
+
+void clutter_pick_stack_log_pick (ClutterPickStack       *pick_stack,
+                                  const graphene_point_t  vertices[4],
+                                  ClutterActor           *actor);
+
+void clutter_pick_stack_push_clip (ClutterPickStack       *pick_stack,
+                                   const graphene_point_t  vertices[4]);
+
+void clutter_pick_stack_pop_clip (ClutterPickStack *pick_stack);
+
+void clutter_pick_stack_push_transform (ClutterPickStack        *pick_stack,
+                                        const graphene_matrix_t *transform);
+
+void clutter_pick_stack_get_transform (ClutterPickStack  *pick_stack,
+                                       graphene_matrix_t *out_transform);
+
+void clutter_pick_stack_pop_transform (ClutterPickStack *pick_stack);
+
+ClutterActor * clutter_pick_stack_find_actor_at (ClutterPickStack *pick_stack,
+                                                 float             x,
+                                                 float             y);
+
+G_END_DECLS
+
+#endif /* CLUTTER_PICK_STACK_PRIVATE_H */
diff --git a/clutter/clutter/clutter-pick-stack.c b/clutter/clutter/clutter-pick-stack.c
new file mode 100644
index 0000000000..1f0c47a214
--- /dev/null
+++ b/clutter/clutter/clutter-pick-stack.c
@@ -0,0 +1,386 @@
+/*
+ * Copyright (C) 2020 Endless OS Foundation, LLC
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "clutter-pick-stack-private.h"
+#include "clutter-private.h"
+
+typedef struct
+{
+  graphene_point_t vertices[4];
+  ClutterActor *actor;
+  int clip_index;
+} PickRecord;
+
+typedef struct
+{
+  int prev;
+  graphene_point_t vertices[4];
+} PickClipRecord;
+
+struct _ClutterPickStack
+{
+  grefcount ref_count;
+
+  CoglMatrixStack *matrix_stack;
+  GArray *vertices_stack;
+  GArray *clip_stack;
+  int current_clip_stack_top;
+
+  gboolean sealed : 1;
+};
+
+G_DEFINE_BOXED_TYPE (ClutterPickStack, clutter_pick_stack,
+                     clutter_pick_stack_ref, clutter_pick_stack_unref)
+
+static gboolean
+is_quadrilateral_axis_aligned_rectangle (const graphene_point_t vertices[4])
+{
+  int i;
+
+  for (i = 0; i < 4; i++)
+    {
+      if (!G_APPROX_VALUE (vertices[i].x,
+                           vertices[(i + 1) % 4].x,
+                           FLT_EPSILON) &&
+          !G_APPROX_VALUE (vertices[i].y,
+                           vertices[(i + 1) % 4].y,
+                           FLT_EPSILON))
+        return FALSE;
+    }
+  return TRUE;
+}
+
+static gboolean
+is_inside_axis_aligned_rectangle (const graphene_point_t *point,
+                                  const graphene_point_t  vertices[4])
+{
+  float min_x = FLT_MAX;
+  float max_x = -FLT_MAX;
+  float min_y = FLT_MAX;
+  float max_y = -FLT_MAX;
+  int i;
+
+  for (i = 0; i < 4; i++)
+    {
+      min_x = MIN (min_x, vertices[i].x);
+      min_y = MIN (min_y, vertices[i].y);
+      max_x = MAX (max_x, vertices[i].x);
+      max_y = MAX (max_y, vertices[i].y);
+    }
+
+  return (point->x >= min_x &&
+          point->y >= min_y &&
+          point->x < max_x &&
+          point->y < max_y);
+}
+
+static int
+clutter_point_compare_line (const graphene_point_t *p,
+                            const graphene_point_t *a,
+                            const graphene_point_t *b)
+{
+  graphene_vec3_t vec_pa;
+  graphene_vec3_t vec_pb;
+  graphene_vec3_t cross;
+  float cross_z;
+
+  graphene_vec3_init (&vec_pa, p->x - a->x, p->y - a->y, 0.f);
+  graphene_vec3_init (&vec_pb, p->x - b->x, p->y - b->y, 0.f);
+  graphene_vec3_cross (&vec_pa, &vec_pb, &cross);
+  cross_z = graphene_vec3_get_z (&cross);
+
+  if (cross_z > 0.f)
+    return 1;
+  else if (cross_z < 0.f)
+    return -1;
+  else
+    return 0;
+}
+
+static gboolean
+is_inside_unaligned_rectangle (const graphene_point_t *point,
+                               const graphene_point_t  vertices[4])
+{
+  unsigned int i;
+  int first_side;
+
+  first_side = 0;
+
+  for (i = 0; i < 4; i++)
+    {
+      int side;
+
+      side = clutter_point_compare_line (point,
+                                         &vertices[i],
+                                         &vertices[(i + 1) % 4]);
+
+      if (side)
+        {
+          if (first_side == 0)
+            first_side = side;
+          else if (side != first_side)
+            return FALSE;
+        }
+    }
+
+  if (first_side == 0)
+    return FALSE;
+
+  return TRUE;
+}
+
+static gboolean
+is_inside_input_region (const graphene_point_t *point,
+                        const graphene_point_t  vertices[4])
+{
+
+  if (is_quadrilateral_axis_aligned_rectangle (vertices))
+    return is_inside_axis_aligned_rectangle (point, vertices);
+  else
+    return is_inside_unaligned_rectangle (point, vertices);
+}
+
+static gboolean
+pick_record_contains_point (ClutterPickStack *pick_stack,
+                            const PickRecord *rec,
+                            float             x,
+                            float             y)
+{
+  const graphene_point_t point = GRAPHENE_POINT_INIT (x, y);
+  int clip_index;
+
+  if (!is_inside_input_region (&point, rec->vertices))
+    return FALSE;
+
+  clip_index = rec->clip_index;
+  while (clip_index >= 0)
+    {
+      const PickClipRecord *clip =
+        &g_array_index (pick_stack->clip_stack, PickClipRecord, clip_index);
+
+      if (!is_inside_input_region (&point, clip->vertices))
+        return FALSE;
+
+      clip_index = clip->prev;
+    }
+
+  return TRUE;
+}
+
+static void
+add_pick_stack_weak_refs (ClutterPickStack *pick_stack)
+{
+  int i;
+
+  g_assert (!pick_stack->sealed);
+
+  for (i = 0; i < pick_stack->vertices_stack->len; i++)
+    {
+      PickRecord *rec =
+        &g_array_index (pick_stack->vertices_stack, PickRecord, i);
+
+      if (rec->actor)
+        g_object_add_weak_pointer (G_OBJECT (rec->actor),
+                                   (gpointer) &rec->actor);
+    }
+}
+
+static void
+remove_pick_stack_weak_refs (ClutterPickStack *pick_stack)
+{
+  int i;
+
+  for (i = 0; i < pick_stack->vertices_stack->len; i++)
+    {
+      PickRecord *rec =
+        &g_array_index (pick_stack->vertices_stack, PickRecord, i);
+
+      if (rec->actor)
+        g_object_remove_weak_pointer (G_OBJECT (rec->actor),
+                                      (gpointer) &rec->actor);
+    }
+}
+
+static void
+clutter_pick_stack_dispose (ClutterPickStack *pick_stack)
+{
+  remove_pick_stack_weak_refs (pick_stack);
+  g_clear_pointer (&pick_stack->matrix_stack, cogl_object_unref);
+  g_clear_pointer (&pick_stack->vertices_stack, g_array_unref);
+  g_clear_pointer (&pick_stack->clip_stack, g_array_unref);
+}
+
+/**
+ * clutter_pick_stack_new:
+ * @context: a #CoglContext
+ *
+ * Creates a new #ClutterPickStack.
+ *
+ * Returns: (transfer full): A newly created #ClutterPickStack
+ */
+ClutterPickStack *
+clutter_pick_stack_new (CoglContext *context)
+{
+  ClutterPickStack *pick_stack;
+
+  pick_stack = g_new0 (ClutterPickStack, 1);
+  g_ref_count_init (&pick_stack->ref_count);
+  pick_stack->matrix_stack = cogl_matrix_stack_new (context);
+  pick_stack->vertices_stack = g_array_new (FALSE, FALSE, sizeof (PickRecord));
+  pick_stack->clip_stack = g_array_new (FALSE, FALSE, sizeof (PickClipRecord));
+  pick_stack->current_clip_stack_top = -1;
+
+  return pick_stack;
+}
+
+/**
+ * clutter_pick_stack_ref:
+ * @pick_stack: A #ClutterPickStack
+ *
+ * Increments the reference count of @pick_stack by one.
+ *
+ * Returns: (transfer full): @pick_stack
+ */
+ClutterPickStack *
+clutter_pick_stack_ref (ClutterPickStack *pick_stack)
+{
+  g_ref_count_inc (&pick_stack->ref_count);
+  return pick_stack;
+}
+
+/**
+ * clutter_pick_stack_unref:
+ * @pick_stack: A #ClutterPickStack
+ *
+ * Decrements the reference count of @pick_stack by one, freeing the structure
+ * when the reference count reaches zero.
+ */
+void
+clutter_pick_stack_unref (ClutterPickStack *pick_stack)
+{
+  if (g_ref_count_dec (&pick_stack->ref_count))
+    {
+      clutter_pick_stack_dispose (pick_stack);
+      g_free (pick_stack);
+    }
+}
+
+void
+clutter_pick_stack_seal (ClutterPickStack *pick_stack)
+{
+  g_assert (!pick_stack->sealed);
+  add_pick_stack_weak_refs (pick_stack);
+  pick_stack->sealed = TRUE;
+}
+
+void
+clutter_pick_stack_log_pick (ClutterPickStack       *pick_stack,
+                             const graphene_point_t  vertices[4],
+                             ClutterActor           *actor)
+{
+  PickRecord rec;
+
+  g_return_if_fail (actor != NULL);
+
+  g_assert (!pick_stack->sealed);
+
+  memcpy (rec.vertices, vertices, 4 * sizeof (graphene_point_t));
+  rec.actor = actor;
+  rec.clip_index = pick_stack->current_clip_stack_top;
+
+  g_array_append_val (pick_stack->vertices_stack, rec);
+}
+
+void
+clutter_pick_stack_push_clip (ClutterPickStack       *pick_stack,
+                              const graphene_point_t  vertices[4])
+{
+  PickClipRecord clip;
+
+  g_assert (!pick_stack->sealed);
+
+  clip.prev = pick_stack->current_clip_stack_top;
+  memcpy (clip.vertices, vertices, 4 * sizeof (graphene_point_t));
+
+  g_array_append_val (pick_stack->clip_stack, clip);
+  pick_stack->current_clip_stack_top = pick_stack->clip_stack->len - 1;
+}
+
+void
+clutter_pick_stack_pop_clip (ClutterPickStack *pick_stack)
+{
+  const PickClipRecord *top;
+
+  g_assert (!pick_stack->sealed);
+  g_assert (pick_stack->current_clip_stack_top >= 0);
+
+  /* Individual elements of clip_stack are not freed. This is so they can
+   * be shared as part of a tree of different stacks used by different
+   * actors in the pick_stack. The whole clip_stack does however get
+   * freed later in clutter_pick_stack_dispose.
+   */
+
+  top = &g_array_index (pick_stack->clip_stack,
+                        PickClipRecord,
+                        pick_stack->current_clip_stack_top);
+
+  pick_stack->current_clip_stack_top = top->prev;
+}
+
+void
+clutter_pick_stack_push_transform (ClutterPickStack        *pick_stack,
+                                   const graphene_matrix_t *transform)
+{
+  cogl_matrix_stack_push (pick_stack->matrix_stack);
+  cogl_matrix_stack_multiply (pick_stack->matrix_stack, transform);
+}
+
+void
+clutter_pick_stack_get_transform (ClutterPickStack  *pick_stack,
+                                  graphene_matrix_t *out_transform)
+{
+  cogl_matrix_stack_get (pick_stack->matrix_stack, out_transform);
+}
+
+void
+clutter_pick_stack_pop_transform (ClutterPickStack *pick_stack)
+{
+  cogl_matrix_stack_pop (pick_stack->matrix_stack);
+}
+
+ClutterActor *
+clutter_pick_stack_find_actor_at (ClutterPickStack *pick_stack,
+                                  float             x,
+                                  float             y)
+{
+  int i;
+
+  /* Search all "painted" pickable actors from front to back. A linear search
+   * is required, and also performs fine since there is typically only
+   * on the order of dozens of actors in the list (on screen) at a time.
+   */
+  for (i = pick_stack->vertices_stack->len - 1; i >= 0; i--)
+    {
+      const PickRecord *rec =
+        &g_array_index (pick_stack->vertices_stack, PickRecord, i);
+
+      if (rec->actor && pick_record_contains_point (pick_stack, rec, x, y))
+        return rec->actor;
+    }
+
+  return NULL;
+}
diff --git a/clutter/clutter/clutter-stage-private.h b/clutter/clutter/clutter-stage-private.h
index a80539666b..85f54e4976 100644
--- a/clutter/clutter/clutter-stage-private.h
+++ b/clutter/clutter/clutter-stage-private.h
@@ -81,15 +81,6 @@ void     _clutter_stage_process_queued_events             (ClutterStage *stage);
 void     _clutter_stage_update_input_devices              (ClutterStage *stage);
 gboolean _clutter_stage_has_full_redraw_queued            (ClutterStage *stage);
 
-void clutter_stage_log_pick (ClutterStage           *stage,
-                             const graphene_point_t *vertices,
-                             ClutterActor           *actor);
-
-void clutter_stage_push_pick_clip (ClutterStage           *stage,
-                                   const graphene_point_t *vertices);
-
-void clutter_stage_pop_pick_clip (ClutterStage *stage);
-
 ClutterActor *_clutter_stage_do_pick (ClutterStage    *stage,
                                       float            x,
                                       float            y,
diff --git a/clutter/clutter/clutter-stage.c b/clutter/clutter/clutter-stage.c
index 53a8ec9b8c..350b1b4a3e 100644
--- a/clutter/clutter/clutter-stage.c
+++ b/clutter/clutter/clutter-stage.c
@@ -125,10 +125,7 @@ struct _ClutterStagePrivate
   GTimer *fps_timer;
   gint32 timer_n_frames;
 
-  GArray *pick_stack;
-  GArray *pick_clip_stack;
-  int pick_clip_stack_top;
-  gboolean pick_stack_frozen;
+  ClutterPickStack *pick_stack;
   ClutterPickMode cached_pick_mode;
 
 #ifdef CLUTTER_ENABLE_DEBUG
@@ -237,266 +234,15 @@ clutter_stage_get_preferred_height (ClutterActor *self,
     *natural_height_p = geom.height;
 }
 
-static void
-add_pick_stack_weak_refs (ClutterStage *stage)
-{
-  ClutterStagePrivate *priv = stage->priv;
-  int i;
-
-  if (priv->pick_stack_frozen)
-    return;
-
-  for (i = 0; i < priv->pick_stack->len; i++)
-    {
-      PickRecord *rec = &g_array_index (priv->pick_stack, PickRecord, i);
-
-      if (rec->actor)
-        g_object_add_weak_pointer (G_OBJECT (rec->actor),
-                                   (gpointer) &rec->actor);
-    }
-
-  priv->pick_stack_frozen = TRUE;
-}
-
-static void
-remove_pick_stack_weak_refs (ClutterStage *stage)
-{
-  ClutterStagePrivate *priv = stage->priv;
-  int i;
-
-  if (!priv->pick_stack_frozen)
-    return;
-
-  for (i = 0; i < priv->pick_stack->len; i++)
-    {
-      PickRecord *rec = &g_array_index (priv->pick_stack, PickRecord, i);
-
-      if (rec->actor)
-        g_object_remove_weak_pointer (G_OBJECT (rec->actor),
-                                      (gpointer) &rec->actor);
-    }
-
-  priv->pick_stack_frozen = FALSE;
-}
-
 static void
 _clutter_stage_clear_pick_stack (ClutterStage *stage)
 {
   ClutterStagePrivate *priv = stage->priv;
 
-  remove_pick_stack_weak_refs (stage);
-  g_array_set_size (priv->pick_stack, 0);
-  g_array_set_size (priv->pick_clip_stack, 0);
-  priv->pick_clip_stack_top = -1;
+  g_clear_pointer (&priv->pick_stack, clutter_pick_stack_unref);
   priv->cached_pick_mode = CLUTTER_PICK_NONE;
 }
 
-void
-clutter_stage_log_pick (ClutterStage           *stage,
-                        const graphene_point_t *vertices,
-                        ClutterActor           *actor)
-{
-  ClutterStagePrivate *priv;
-  PickRecord rec;
-
-  g_return_if_fail (CLUTTER_IS_STAGE (stage));
-  g_return_if_fail (actor != NULL);
-
-  priv = stage->priv;
-
-  g_assert (!priv->pick_stack_frozen);
-
-  memcpy (rec.vertex, vertices, 4 * sizeof (graphene_point_t));
-  rec.actor = actor;
-  rec.clip_stack_top = priv->pick_clip_stack_top;
-
-  g_array_append_val (priv->pick_stack, rec);
-}
-
-void
-clutter_stage_push_pick_clip (ClutterStage           *stage,
-                              const graphene_point_t *vertices)
-{
-  ClutterStagePrivate *priv;
-  PickClipRecord clip;
-
-  g_return_if_fail (CLUTTER_IS_STAGE (stage));
-
-  priv = stage->priv;
-
-  g_assert (!priv->pick_stack_frozen);
-
-  clip.prev = priv->pick_clip_stack_top;
-  memcpy (clip.vertex, vertices, 4 * sizeof (graphene_point_t));
-
-  g_array_append_val (priv->pick_clip_stack, clip);
-  priv->pick_clip_stack_top = priv->pick_clip_stack->len - 1;
-}
-
-void
-clutter_stage_pop_pick_clip (ClutterStage *stage)
-{
-  ClutterStagePrivate *priv;
-  const PickClipRecord *top;
-
-  g_return_if_fail (CLUTTER_IS_STAGE (stage));
-
-  priv = stage->priv;
-
-  g_assert (!priv->pick_stack_frozen);
-  g_assert (priv->pick_clip_stack_top >= 0);
-
-  /* Individual elements of pick_clip_stack are not freed. This is so they
-   * can be shared as part of a tree of different stacks used by different
-   * actors in the pick_stack. The whole pick_clip_stack does however get
-   * freed later in _clutter_stage_clear_pick_stack.
-   */
-
-  top = &g_array_index (priv->pick_clip_stack,
-                        PickClipRecord,
-                        priv->pick_clip_stack_top);
-
-  priv->pick_clip_stack_top = top->prev;
-}
-
-static gboolean
-is_quadrilateral_axis_aligned_rectangle (const graphene_point_t *vertices)
-{
-  int i;
-
-  for (i = 0; i < 4; i++)
-    {
-      if (!G_APPROX_VALUE (vertices[i].x,
-                           vertices[(i + 1) % 4].x,
-                           FLT_EPSILON) &&
-          !G_APPROX_VALUE (vertices[i].y,
-                           vertices[(i + 1) % 4].y,
-                           FLT_EPSILON))
-        return FALSE;
-    }
-  return TRUE;
-}
-
-static gboolean
-is_inside_axis_aligned_rectangle (const graphene_point_t *point,
-                                  const graphene_point_t *vertices)
-{
-  float min_x = FLT_MAX;
-  float max_x = -FLT_MAX;
-  float min_y = FLT_MAX;
-  float max_y = -FLT_MAX;
-  int i;
-
-  for (i = 0; i < 3; i++)
-    {
-      min_x = MIN (min_x, vertices[i].x);
-      min_y = MIN (min_y, vertices[i].y);
-      max_x = MAX (max_x, vertices[i].x);
-      max_y = MAX (max_y, vertices[i].y);
-    }
-
-  return (point->x >= min_x &&
-          point->y >= min_y &&
-          point->x < max_x &&
-          point->y < max_y);
-}
-
-static int
-clutter_point_compare_line (const graphene_point_t *p,
-                            const graphene_point_t *a,
-                            const graphene_point_t *b)
-{
-  graphene_vec3_t vec_pa;
-  graphene_vec3_t vec_pb;
-  graphene_vec3_t cross;
-  float cross_z;
-
-  graphene_vec3_init (&vec_pa, p->x - a->x, p->y - a->y, 0.f);
-  graphene_vec3_init (&vec_pb, p->x - b->x, p->y - b->y, 0.f);
-  graphene_vec3_cross (&vec_pa, &vec_pb, &cross);
-  cross_z = graphene_vec3_get_z (&cross);
-
-  if (cross_z > 0.f)
-    return 1;
-  else if (cross_z < 0.f)
-    return -1;
-  else
-    return 0;
-}
-
-static gboolean
-is_inside_unaligned_rectangle (const graphene_point_t *point,
-                               const graphene_point_t *vertices)
-{
-  unsigned int i;
-  int first_side;
-
-  first_side = 0;
-
-  for (i = 0; i < 4; i++)
-    {
-      int side;
-
-      side = clutter_point_compare_line (point,
-                                         &vertices[i],
-                                         &vertices[(i + 1) % 4]);
-
-      if (side)
-        {
-          if (first_side == 0)
-            first_side = side;
-          else if (side != first_side)
-            return FALSE;
-        }
-    }
-
-  if (first_side == 0)
-    return FALSE;
-
-  return TRUE;
-}
-
-static gboolean
-is_inside_input_region (const graphene_point_t *point,
-                        const graphene_point_t *vertices)
-{
-
-  if (is_quadrilateral_axis_aligned_rectangle (vertices))
-    return is_inside_axis_aligned_rectangle (point, vertices);
-  else
-    return is_inside_unaligned_rectangle (point, vertices);
-}
-
-static gboolean
-pick_record_contains_point (ClutterStage     *stage,
-                            const PickRecord *rec,
-                            float             x,
-                            float             y)
-{
-  const graphene_point_t point = GRAPHENE_POINT_INIT (x, y);
-  ClutterStagePrivate *priv;
-  int clip_index;
-
-  if (!is_inside_input_region (&point, rec->vertex))
-      return FALSE;
-
-  priv = stage->priv;
-  clip_index = rec->clip_stack_top;
-  while (clip_index >= 0)
-    {
-      const PickClipRecord *clip = &g_array_index (priv->pick_clip_stack,
-                                                   PickClipRecord,
-                                                   clip_index);
-
-      if (!is_inside_input_region (&point, clip->vertex))
-        return FALSE;
-
-      clip_index = clip->prev;
-    }
-
-  return TRUE;
-}
-
 static void
 clutter_stage_add_redraw_clip (ClutterStage          *stage,
                                cairo_rectangle_int_t *clip)
@@ -1392,11 +1138,11 @@ _clutter_stage_do_pick_on_view (ClutterStage     *stage,
                                 ClutterStageView *view)
 {
   ClutterStagePrivate *priv = stage->priv;
-  int i;
+  ClutterActor *actor;
 
   COGL_TRACE_BEGIN_SCOPED (ClutterStagePickView, "Pick (view)");
 
-  if (mode != priv->cached_pick_mode)
+  if (!priv->pick_stack || mode != priv->cached_pick_mode)
     {
       ClutterPickContext *pick_context;
 
@@ -1405,26 +1151,14 @@ _clutter_stage_do_pick_on_view (ClutterStage     *stage,
       pick_context = clutter_pick_context_new_for_view (view, mode);
 
       clutter_actor_pick (CLUTTER_ACTOR (stage), pick_context);
+      priv->pick_stack = clutter_pick_context_steal_stack (pick_context);
       priv->cached_pick_mode = mode;
 
       clutter_pick_context_destroy (pick_context);
-
-      add_pick_stack_weak_refs (stage);
-    }
-
-  /* Search all "painted" pickable actors from front to back. A linear search
-   * is required, and also performs fine since there is typically only
-   * on the order of dozens of actors in the list (on screen) at a time.
-   */
-  for (i = priv->pick_stack->len - 1; i >= 0; i--)
-    {
-      const PickRecord *rec = &g_array_index (priv->pick_stack, PickRecord, i);
-
-      if (rec->actor && pick_record_contains_point (stage, rec, x, y))
-        return rec->actor;
     }
 
-  return CLUTTER_ACTOR (stage);
+  actor = clutter_pick_stack_find_actor_at (priv->pick_stack, x, y);
+  return actor ? actor : CLUTTER_ACTOR (stage);
 }
 
 /**
@@ -1636,8 +1370,6 @@ clutter_stage_finalize (GObject *object)
   g_array_free (priv->paint_volume_stack, TRUE);
 
   _clutter_stage_clear_pick_stack (stage);
-  g_array_free (priv->pick_clip_stack, TRUE);
-  g_array_free (priv->pick_stack, TRUE);
 
   if (priv->fps_timer != NULL)
     g_timer_destroy (priv->fps_timer);
@@ -1953,9 +1685,6 @@ clutter_stage_init (ClutterStage *self)
   priv->paint_volume_stack =
     g_array_new (FALSE, FALSE, sizeof (ClutterPaintVolume));
 
-  priv->pick_stack = g_array_new (FALSE, FALSE, sizeof (PickRecord));
-  priv->pick_clip_stack = g_array_new (FALSE, FALSE, sizeof (PickClipRecord));
-  priv->pick_clip_stack_top = -1;
   priv->cached_pick_mode = CLUTTER_PICK_NONE;
 }
 
diff --git a/clutter/clutter/meson.build b/clutter/clutter/meson.build
index 18994df8d1..f914370ec4 100644
--- a/clutter/clutter/meson.build
+++ b/clutter/clutter/meson.build
@@ -150,6 +150,7 @@ clutter_sources = [
   'clutter-path-constraint.c',
   'clutter-path.c',
   'clutter-pick-context.c',
+  'clutter-pick-stack.c',
   'clutter-property-transition.c',
   'clutter-rotate-action.c',
   'clutter-script.c',


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]