Compare commits

...

3 Commits

Author SHA1 Message Date
Emmanuele Bassi 8c7dc4ea38 actor: Add hit testing on ClutterActor
We should have a way to determine whether a point, defined in
actor-relative coordinates, is contained by an actor or not, as well as
determining the innermost child of a ClutterActor that contains the
point.

This requires some minimal matrix calculations to unproject a point
using the modelview and projection matrix tied to an actor, as well as a
new virtual function to allow actors to define non-rectangular shapes
for themselves.
2012-05-08 17:51:56 +01:00
Emmanuele Bassi 55c4d7ab67 stage: Drop the custom pick() implementation
Now that clutter_actor_should_pick_paint() returns FALSE when called on
a top-level, we can drop the pick() override inside ClutterStage; the
default pick() implementation in ClutterActor calls should_pick_paint()
in order to decide whether or not the pick color should be used to
submit state and geometry. Since children of an Actor are painted
regardless of the pick color, the default pick() implementation inside
ClutterActor is now exactly the same as the override in ClutteStage.
2012-05-08 17:12:28 +01:00
Emmanuele Bassi c5e1a34408 actor: Skip top-levels in should_pick_paint()
When calling the clutter_actor_should_pick_paint() function, we should
also skip if the actor is a top-level.

The Stage is automatically cleared with the right color when picking,
as an optimization so that we don't have to submit further geometry or
change state; this means that calling should_pick_paint() should always
return FALSE on a Stage.
2012-05-08 17:12:28 +01:00
11 changed files with 316 additions and 55 deletions

View File

@ -2082,7 +2082,8 @@ clutter_actor_should_pick_paint (ClutterActor *self)
{
g_return_val_if_fail (CLUTTER_IS_ACTOR (self), FALSE);
if (CLUTTER_ACTOR_IS_MAPPED (self) &&
if (!CLUTTER_ACTOR_IS_TOPLEVEL (self) &&
CLUTTER_ACTOR_IS_MAPPED (self) &&
(_clutter_context_get_pick_mode () == CLUTTER_PICK_ALL ||
CLUTTER_ACTOR_IS_REACTIVE (self)))
return TRUE;
@ -5465,6 +5466,30 @@ clutter_actor_real_destroy (ClutterActor *actor)
g_object_thaw_notify (G_OBJECT (actor));
}
static gboolean
clutter_actor_real_contains_point (ClutterActor *self,
const ClutterPoint *point)
{
ClutterActorPrivate *priv = self->priv;
ClutterRect bounds;
CLUTTER_NOTE (HIT_TEST, "Point { x:%.2f, y:%.2f } -> '%s' { %.2f, %.2f, %.2f, %.2f }",
_clutter_actor_get_debug_name (self),
point->x, point->y,
priv->allocation.x1,
priv->allocation.y1,
priv->allocation.x2,
priv->allocation.y2);
clutter_rect_init (&bounds,
0.f,
0.f,
priv->allocation.x2 - priv->allocation.x1,
priv->allocation.y2 - priv->allocation.y1);
return clutter_rect_contains_point (&bounds, point);
}
static GObject *
clutter_actor_constructor (GType gtype,
guint n_props,
@ -5526,6 +5551,7 @@ clutter_actor_class_init (ClutterActorClass *klass)
klass->has_overlaps = clutter_actor_real_has_overlaps;
klass->paint = clutter_actor_real_paint;
klass->destroy = clutter_actor_real_destroy;
klass->contains_point = clutter_actor_real_contains_point;
g_type_class_add_private (klass, sizeof (ClutterActorPrivate));
@ -18535,3 +18561,145 @@ clutter_actor_needs_expand (ClutterActor *self,
return FALSE;
}
/**
* clutter_actor_contains_point:
* @self: a #ClutterActor
* @point: a #ClutterPoint, in actor-relative coordinates
*
* Calls #ClutterActorClass.contains_point() to check if @self contains
* the given @point.
*
* Return value: %TRUE if the #ClutterActor contains the point
*
* Since: 1.12
*/
gboolean
clutter_actor_contains_point (ClutterActor *self,
const ClutterPoint *point)
{
g_return_val_if_fail (CLUTTER_IS_ACTOR (self), FALSE);
g_return_val_if_fail (point != NULL, FALSE);
return CLUTTER_ACTOR_GET_CLASS (self)->contains_point (self, point);
}
static gboolean
actor_is_transformed (ClutterActor *actor)
{
const ClutterTransformInfo *info;
info = _clutter_actor_get_transform_info_or_defaults (actor);
if (info->depth != 0.f)
return TRUE;
if (info->rx_angle || info->ry_angle || info->rz_angle)
return TRUE;
if (info->scale_x != 1.f || info->scale_y != 1.f)
return TRUE;
if (!clutter_anchor_coord_is_zero (&info->anchor))
return TRUE;
return FALSE;
}
/**
* clutter_actor_hit_test:
* @self: a #ClutterActor
* @point: a #ClutterPoint, in actor-relative coordinates
*
* Finds the innermost #ClutterActor that contains the given @point.
*
* This function will recurse through the actor tree starting from @self
* and will implicitly call #ClutterActorClass.contains_point() to check
* if an actor contains the given point.
*
* Return value: (transfer none): a #ClutterActor, or %NULL
*
* Since: 1.12
*/
ClutterActor *
clutter_actor_hit_test (ClutterActor *self,
const ClutterPoint *point)
{
ClutterActor *retval, *child;
ClutterActorIter iter;
const CoglMatrix *projection;
float viewport[4];
ClutterStage *stage;
g_return_val_if_fail (CLUTTER_IS_ACTOR (self), NULL);
g_return_val_if_fail (point != NULL, NULL);
if (!clutter_actor_contains_point (self, point))
{
CLUTTER_NOTE (HIT_TEST,
"The actor '%s' does not contain { x:%.2f, y:%.2f }",
_clutter_actor_get_debug_name (self),
point->x, point->y);
return NULL;
}
stage = (ClutterStage *) _clutter_actor_get_stage_internal (self);
if (stage == NULL)
return NULL;
/* XXX - need peek() versions of this */
projection = _clutter_stage_peek_projection_matrix (stage);
/* the viewport is defined to be the allocation of the actor */
viewport[0] = 0.f;
viewport[1] = 0.f;
viewport[2] = self->priv->allocation.x2 - self->priv->allocation.x1;
viewport[3] = self->priv->allocation.y2 - self->priv->allocation.y1;
clutter_actor_iter_init (&iter, self);
while (clutter_actor_iter_next (&iter, &child))
{
ClutterPoint child_point;
if (!actor_is_transformed (child))
{
/* fast path: just offset the coordinates */
child_point.x = point->x - child->priv->allocation.x1;
child_point.y = point->y - child->priv->allocation.y1;
}
else
{
ClutterVertex out_vertex = CLUTTER_VERTEX_INIT (0.f, 0.f, 0.f);
ClutterVertex in_vertex = CLUTTER_VERTEX_INIT (point->x, point->y, 0.f);
/* not so fast path: unproject the point */
if (!_clutter_util_unproject (&in_vertex,
&child->priv->transform,
projection,
viewport,
&out_vertex))
{
CLUTTER_NOTE (HIT_TEST, "Skipping '%s': unproject failed",
_clutter_actor_get_debug_name (child));
continue;
}
child_point.x = out_vertex.x - child->priv->allocation.x1;
child_point.y = out_vertex.y - child->priv->allocation.y1;
}
retval = clutter_actor_hit_test (child, &child_point);
if (retval != NULL)
{
CLUTTER_NOTE (HIT_TEST,
"Hit test success: '%s'",
_clutter_actor_get_debug_name (retval));
return retval;
}
}
CLUTTER_NOTE (HIT_TEST,
"Hit test success: '%s'",
_clutter_actor_get_debug_name (self));
return self;
}

View File

@ -262,10 +262,12 @@ struct _ClutterActorClass
void (* paint_node) (ClutterActor *self,
ClutterPaintNode *root);
gboolean (* contains_point) (ClutterActor *self,
const ClutterPoint *point);
/*< private >*/
/* padding for future expansion */
gpointer _padding_dummy[27];
gpointer _padding_dummy[26];
};
/**
@ -527,6 +529,10 @@ gboolean clutter_actor_event
ClutterEvent *event,
gboolean capture);
gboolean clutter_actor_has_pointer (ClutterActor *self);
gboolean clutter_actor_contains_point (ClutterActor *self,
const ClutterPoint *point);
ClutterActor * clutter_actor_hit_test (ClutterActor *self,
const ClutterPoint *point);
/* Text */
PangoContext * clutter_actor_get_pango_context (ClutterActor *self);

View File

@ -895,8 +895,8 @@ clutter_rect_get_center (ClutterRect *rect,
* Since: 1.12
*/
gboolean
clutter_rect_contains_point (ClutterRect *rect,
ClutterPoint *point)
clutter_rect_contains_point (ClutterRect *rect,
const ClutterPoint *point)
{
g_return_val_if_fail (rect != NULL, FALSE);
g_return_val_if_fail (point != NULL, FALSE);

View File

@ -23,7 +23,8 @@ typedef enum {
CLUTTER_DEBUG_PICK = 1 << 13,
CLUTTER_DEBUG_EVENTLOOP = 1 << 14,
CLUTTER_DEBUG_CLIPPING = 1 << 15,
CLUTTER_DEBUG_OOB_TRANSFORMS = 1 << 16
CLUTTER_DEBUG_OOB_TRANSFORMS = 1 << 16,
CLUTTER_DEBUG_HIT_TEST = 1 << 17
} ClutterDebugFlag;
typedef enum {

View File

@ -192,6 +192,7 @@ static const GDebugKey clutter_debug_keys[] = {
{ "layout", CLUTTER_DEBUG_LAYOUT },
{ "clipping", CLUTTER_DEBUG_CLIPPING },
{ "oob-transforms", CLUTTER_DEBUG_OOB_TRANSFORMS },
{ "hit-test", CLUTTER_DEBUG_HIT_TEST },
};
#endif /* CLUTTER_ENABLE_DEBUG */

View File

@ -253,6 +253,12 @@ void _clutter_util_rectangle_union (const cairo_rectangle_int_t *src1,
const cairo_rectangle_int_t *src2,
cairo_rectangle_int_t *dest);
gboolean _clutter_util_unproject (const ClutterVertex *in_vertex,
const CoglMatrix *modelview,
const CoglMatrix *projection,
const float *viewport,
ClutterVertex *out_vertex);
typedef struct _ClutterPlane
{
float v0[3];

View File

@ -44,6 +44,7 @@ void _clutter_stage_set_window (ClutterStage
ClutterStageWindow *_clutter_stage_get_window (ClutterStage *stage);
void _clutter_stage_get_projection_matrix (ClutterStage *stage,
CoglMatrix *projection);
const CoglMatrix * _clutter_stage_peek_projection_matrix (ClutterStage *stage);
void _clutter_stage_dirty_projection (ClutterStage *stage);
void _clutter_stage_set_viewport (ClutterStage *stage,
float x,

View File

@ -714,22 +714,6 @@ clutter_stage_paint (ClutterActor *self)
clutter_actor_paint (child);
}
static void
clutter_stage_pick (ClutterActor *self,
const ClutterColor *color)
{
ClutterActorIter iter;
ClutterActor *child;
/* Note: we don't chain up to our parent as we don't want any geometry
* emitted for the stage itself. The stage's pick id is effectively handled
* by the call to cogl_clear done in clutter-main.c:_clutter_do_pick_async()
*/
clutter_actor_iter_init (&iter, self);
while (clutter_actor_iter_next (&iter, &child))
clutter_actor_paint (child);
}
static gboolean
clutter_stage_get_paint_volume (ClutterActor *self,
ClutterPaintVolume *volume)
@ -1864,7 +1848,6 @@ clutter_stage_class_init (ClutterStageClass *klass)
actor_class->get_preferred_width = clutter_stage_get_preferred_width;
actor_class->get_preferred_height = clutter_stage_get_preferred_height;
actor_class->paint = clutter_stage_paint;
actor_class->pick = clutter_stage_pick;
actor_class->get_paint_volume = clutter_stage_get_paint_volume;
actor_class->realize = clutter_stage_realize;
actor_class->unrealize = clutter_stage_unrealize;
@ -2488,6 +2471,12 @@ _clutter_stage_get_projection_matrix (ClutterStage *stage,
*projection = stage->priv->projection;
}
const CoglMatrix *
_clutter_stage_peek_projection_matrix (ClutterStage *stage)
{
return &stage->priv->projection;
}
/* This simply provides a simple mechanism for us to ensure that
* the projection matrix gets re-asserted before painting.
*

View File

@ -297,56 +297,56 @@ GType clutter_rect_get_type (void) G_GNUC_CONST;
CLUTTER_AVAILABLE_IN_1_12
ClutterRect * clutter_rect_alloc (void);
CLUTTER_AVAILABLE_IN_1_12
ClutterRect * clutter_rect_init (ClutterRect *rect,
float x,
float y,
float width,
float height);
ClutterRect * clutter_rect_init (ClutterRect *rect,
float x,
float y,
float width,
float height);
CLUTTER_AVAILABLE_IN_1_12
ClutterRect * clutter_rect_copy (const ClutterRect *rect);
ClutterRect * clutter_rect_copy (const ClutterRect *rect);
CLUTTER_AVAILABLE_IN_1_12
void clutter_rect_free (ClutterRect *rect);
void clutter_rect_free (ClutterRect *rect);
CLUTTER_AVAILABLE_IN_1_12
gboolean clutter_rect_equals (ClutterRect *a,
ClutterRect *b);
gboolean clutter_rect_equals (ClutterRect *a,
ClutterRect *b);
CLUTTER_AVAILABLE_IN_1_12
ClutterRect * clutter_rect_normalize (ClutterRect *rect);
ClutterRect * clutter_rect_normalize (ClutterRect *rect);
CLUTTER_AVAILABLE_IN_1_12
void clutter_rect_get_center (ClutterRect *rect,
ClutterPoint *center);
void clutter_rect_get_center (ClutterRect *rect,
ClutterPoint *center);
CLUTTER_AVAILABLE_IN_1_12
gboolean clutter_rect_contains_point (ClutterRect *rect,
ClutterPoint *point);
gboolean clutter_rect_contains_point (ClutterRect *rect,
const ClutterPoint *point);
CLUTTER_AVAILABLE_IN_1_12
gboolean clutter_rect_contains_rect (ClutterRect *a,
ClutterRect *b);
gboolean clutter_rect_contains_rect (ClutterRect *a,
ClutterRect *b);
CLUTTER_AVAILABLE_IN_1_12
void clutter_rect_union (ClutterRect *a,
ClutterRect *b,
ClutterRect *res);
void clutter_rect_union (ClutterRect *a,
ClutterRect *b,
ClutterRect *res);
CLUTTER_AVAILABLE_IN_1_12
gboolean clutter_rect_intersection (ClutterRect *a,
ClutterRect *b,
ClutterRect *res);
gboolean clutter_rect_intersection (ClutterRect *a,
ClutterRect *b,
ClutterRect *res);
CLUTTER_AVAILABLE_IN_1_12
void clutter_rect_offset (ClutterRect *rect,
float d_x,
float d_y);
void clutter_rect_offset (ClutterRect *rect,
float d_x,
float d_y);
CLUTTER_AVAILABLE_IN_1_12
void clutter_rect_inset (ClutterRect *rect,
float d_x,
float d_y);
void clutter_rect_inset (ClutterRect *rect,
float d_x,
float d_y);
CLUTTER_AVAILABLE_IN_1_12
void clutter_rect_clamp_to_pixel (ClutterRect *rect);
void clutter_rect_clamp_to_pixel (ClutterRect *rect);
CLUTTER_AVAILABLE_IN_1_12
float clutter_rect_get_x (ClutterRect *rect);
float clutter_rect_get_x (ClutterRect *rect);
CLUTTER_AVAILABLE_IN_1_12
float clutter_rect_get_y (ClutterRect *rect);
float clutter_rect_get_y (ClutterRect *rect);
CLUTTER_AVAILABLE_IN_1_12
float clutter_rect_get_width (ClutterRect *rect);
float clutter_rect_get_width (ClutterRect *rect);
CLUTTER_AVAILABLE_IN_1_12
float clutter_rect_get_height (ClutterRect *rect);
float clutter_rect_get_height (ClutterRect *rect);
/**
* ClutterVertex:

View File

@ -182,6 +182,71 @@ _clutter_util_rectangle_union (const cairo_rectangle_int_t *src1,
dest->y = dest_y;
}
/*< private >
* _clutter_util_unproject:
* @in_vertex: a #ClutterVertex with the point to unproject
* @modelview: the modelview matrix used to unproject
* @projection: the projection matrix used to unproject
* @viewport: the viewport, as an array of floats with the origin in the
* first two elements and the width and height in the last two
* @out_vertex: (out caller-allocates): return location for the unprojected
* vertex
*
* Unprojects @in_vertex according to the provided modelview and projection
* matrices, and relative to the viewport.
*
* Return value: %TRUE if the vertex was successfully unprojected
*/
gboolean
_clutter_util_unproject (const ClutterVertex *in_vertex,
const CoglMatrix *modelview,
const CoglMatrix *projection,
const float *viewport,
ClutterVertex *out_vertex)
{
float in_v[4], out_v[4];
const float *matrix;
CoglMatrix final;
int i;
cogl_matrix_multiply (&final, modelview, projection);
if (!cogl_matrix_get_inverse (&final, &final))
return FALSE;
in_v[0] = in_vertex->x;
in_v[1] = in_vertex->y;
in_v[2] = in_vertex->z;
in_v[3] = 1.0f;
/* normalize in GL <-1, 1> space */
in_v[0] = (in_v[0] - viewport[0]) / viewport[2];
in_v[1] = (in_v[1] - viewport[1]) / viewport[3];
in_v[0] = in_v[0] * 2 - 1;
in_v[1] = in_v[1] * 2 - 1;
in_v[2] = in_v[2] * 2 - 1;
/* multiply the matrix with the vector */
matrix = cogl_matrix_get_array (&final);
for (i = 0; i < 4; i++)
{
out_v[i] = in_v[0] * matrix[0 * 4 + i]
+ in_v[1] * matrix[1 * 4 + i]
+ in_v[2] * matrix[2 * 4 + i]
+ in_v[3] * matrix[3 * 4 + i];
}
if (out_v[3] == 0.f)
return FALSE;
/* denormalize using the viewport */
out_vertex->x = MTX_GL_SCALE_X (out_v[0], out_v[3], viewport[2], viewport[0]);
out_vertex->y = MTX_GL_SCALE_Y (out_v[1], out_v[3], viewport[3], viewport[1]);
out_vertex->z = MTX_GL_SCALE_Z (out_v[2], out_v[3], viewport[2], viewport[0]);
return TRUE;
}
typedef struct
{
GType value_type;

View File

@ -83,6 +83,28 @@ animate_rotation (ClutterActor *actor,
return CLUTTER_EVENT_STOP;
}
static gboolean
on_capture (ClutterActor *stage,
ClutterEvent *event)
{
ClutterActor *hit;
ClutterPoint point = { 0, };
if (clutter_event_type (event) != CLUTTER_MOTION)
return CLUTTER_EVENT_PROPAGATE;
clutter_event_get_position (event, &point);
hit = clutter_actor_hit_test (stage, &point);
if (hit != NULL)
g_print ("HIT TEST: %s (at x:%.2f, y:%.2f)\n",
clutter_actor_get_name (hit),
point.x,
point.y);
return CLUTTER_EVENT_PROPAGATE;
}
int
main (int argc, char *argv[])
{
@ -93,9 +115,11 @@ main (int argc, char *argv[])
return EXIT_FAILURE;
stage = clutter_stage_new ();
g_signal_connect (stage, "captured-event", G_CALLBACK (on_capture), NULL);
g_signal_connect (stage, "destroy", G_CALLBACK (clutter_main_quit), NULL);
clutter_stage_set_title (CLUTTER_STAGE (stage), "Three Flowers in a Vase");
clutter_stage_set_user_resizable (CLUTTER_STAGE (stage), TRUE);
clutter_actor_set_name (stage, "Stage");
/* there are three flowers in a vase */
vase = clutter_actor_new ();