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
This commit is contained in:
Georges Basile Stavracas Neto 2020-10-16 19:13:12 -03:00
parent 0d79a0faf8
commit f411834d42
9 changed files with 595 additions and 309 deletions

View File

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

View File

@ -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 */

View File

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

View File

@ -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 */

View File

@ -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 */

View File

@ -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;
}

View File

@ -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,

View File

@ -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;
}

View File

@ -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',