diff --git a/clutter/clutter/clutter-actor.c b/clutter/clutter/clutter-actor.c index 4a92d6ffd..867b54c47 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 7e4422edd..cfbfce6eb 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 6209e58ce..29027284a 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 d420d0a57..4afa25af7 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 000000000..1be943aae --- /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 . + */ + +#ifndef CLUTTER_PICK_STACK_PRIVATE_H +#define CLUTTER_PICK_STACK_PRIVATE_H + +#include + +#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 000000000..1f0c47a21 --- /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 . + */ + +#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 a80539666..85f54e497 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 53a8ec9b8..350b1b4a3 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 18994df8d..f914370ec 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',