mutter/clutter/clutter/clutter-actor-box.c

604 lines
15 KiB
C
Raw Normal View History

#include "clutter-build-config.h"
#include <math.h>
#include "clutter-types.h"
#include "clutter-interval.h"
#include "clutter-private.h"
clutter: Fix offscreen-effect painting of clones `ClutterOffscreenEffect` had been getting the wrong bounding box in the case of clones and descendents of clones, causing visibly incorrect clipping. This was due to `clutter_actor_get_paint_box` only ever being given the source actor during a paint (which is correct) and not the clone. Even if we weren't painting a clone but an offscreened descendent of a clone (like in gnome-shell's desktop zoom), we would get the wrong result. Fortunately we don't need to know the actual clone/actor being painted so don't need to call the problematic `clutter_actor_get_paint_box` at all. The solution is to only keep untransformed rendering in the FBO and leave the correct transformation for later. The correct clone/actor's transformation is already set for us as the current cogl modelview matrix by `clutter_actor_paint`. Bonus optimization: This all means we don't need to keep `last_matrix_drawn` or force a full repaint every time some part of the transformation changes. Because the FBO contents are no longer affected by transformations. As it should be. In other words, offscreen-effected actors can now move around on screen without themselves being repainted. Special thanks to Mai Lavelle for identifying the cause of the problem. Fixes: https://bugzilla.gnome.org/show_bug.cgi?id=789050, https://bugzilla.gnome.org/show_bug.cgi?id=659523#c9, https://gitlab.gnome.org/GNOME/mutter/issues/196, https://gitlab.gnome.org/GNOME/mutter/issues/282, https://gitlab.gnome.org/GNOME/gnome-shell/issues/387, https://launchpad.net/bugs/1767648, https://launchpad.net/bugs/1779615
2018-05-24 09:51:22 +00:00
#include "clutter-actor-box-private.h"
/**
* clutter_actor_box_new:
* @x_1: X coordinate of the top left point
* @y_1: Y coordinate of the top left point
* @x_2: X coordinate of the bottom right point
* @y_2: Y coordinate of the bottom right point
*
* Allocates a new [struct@ActorBox] using the passed coordinates
* for the top left and bottom right points.
*
* This function is the logical equivalent of:
*
* ```c
* clutter_actor_box_init (clutter_actor_box_alloc (),
* x_1, y_1,
* x_2, y_2);
* ```
*
* Return value: (transfer full): the newly allocated #ClutterActorBox.
* Use [method@ActorBox.free] to free the resources
*/
ClutterActorBox *
clutter_actor_box_new (gfloat x_1,
gfloat y_1,
gfloat x_2,
gfloat y_2)
{
return clutter_actor_box_init (clutter_actor_box_alloc (),
x_1, y_1,
x_2, y_2);
}
/**
* clutter_actor_box_alloc:
*
* Allocates a new [struct@ActorBox].
*
* Return value: (transfer full): the newly allocated #ClutterActorBox.
* Use [method@ActorBox.free] to free its resources
*/
ClutterActorBox *
clutter_actor_box_alloc (void)
{
return g_new0 (ClutterActorBox, 1);
}
/**
* clutter_actor_box_init:
* @box: a #ClutterActorBox
* @x_1: X coordinate of the top left point
* @y_1: Y coordinate of the top left point
* @x_2: X coordinate of the bottom right point
* @y_2: Y coordinate of the bottom right point
*
* Initializes @box with the given coordinates.
*
* Return value: (transfer none): the initialized #ClutterActorBox
*/
ClutterActorBox *
clutter_actor_box_init (ClutterActorBox *box,
gfloat x_1,
gfloat y_1,
gfloat x_2,
gfloat y_2)
{
g_return_val_if_fail (box != NULL, NULL);
box->x1 = x_1;
box->y1 = y_1;
box->x2 = x_2;
box->y2 = y_2;
return box;
}
/**
* clutter_actor_box_init_rect:
* @box: a #ClutterActorBox
* @x: X coordinate of the origin
* @y: Y coordinate of the origin
* @width: width of the box
* @height: height of the box
*
* Initializes @box with the given origin and size.
*/
void
clutter_actor_box_init_rect (ClutterActorBox *box,
gfloat x,
gfloat y,
gfloat width,
gfloat height)
{
g_return_if_fail (box != NULL);
box->x1 = x;
box->y1 = y;
box->x2 = box->x1 + width;
box->y2 = box->y1 + height;
}
/**
* clutter_actor_box_copy:
* @box: a #ClutterActorBox
*
* Copies @box
*
* Return value: a newly allocated copy of #ClutterActorBox. Use
* [method@ActorBox.free] to free the allocated resources
*/
ClutterActorBox *
clutter_actor_box_copy (const ClutterActorBox *box)
{
if (G_LIKELY (box != NULL))
return g_memdup2 (box, sizeof (ClutterActorBox));
return NULL;
}
/**
* clutter_actor_box_free:
* @box: a #ClutterActorBox
*
* Frees a #ClutterActorBox allocated using [ctor@ActorBox.new]
* or [method@ActorBox.copy].
*/
void
clutter_actor_box_free (ClutterActorBox *box)
{
if (G_LIKELY (box != NULL))
g_free (box);
}
/**
* clutter_actor_box_equal:
* @box_a: a #ClutterActorBox
* @box_b: a #ClutterActorBox
*
* Checks @box_a and @box_b for equality
*
* Return value: %TRUE if the passed #ClutterActorBox are equal
*/
gboolean
clutter_actor_box_equal (const ClutterActorBox *box_a,
const ClutterActorBox *box_b)
{
g_return_val_if_fail (box_a != NULL && box_b != NULL, FALSE);
if (box_a == box_b)
return TRUE;
return box_a->x1 == box_b->x1 && box_a->y1 == box_b->y1 &&
box_a->x2 == box_b->x2 && box_a->y2 == box_b->y2;
}
/**
* clutter_actor_box_get_x:
* @box: a #ClutterActorBox
*
* Retrieves the X coordinate of the origin of @box
*
* Return value: the X coordinate of the origin
*/
gfloat
clutter_actor_box_get_x (const ClutterActorBox *box)
{
g_return_val_if_fail (box != NULL, 0.);
return box->x1;
}
/**
* clutter_actor_box_get_y:
* @box: a #ClutterActorBox
*
* Retrieves the Y coordinate of the origin of @box
*
* Return value: the Y coordinate of the origin
*/
gfloat
clutter_actor_box_get_y (const ClutterActorBox *box)
{
g_return_val_if_fail (box != NULL, 0.);
return box->y1;
}
/**
* clutter_actor_box_get_width:
* @box: a #ClutterActorBox
*
* Retrieves the width of the @box
*
* Return value: the width of the box
*/
gfloat
clutter_actor_box_get_width (const ClutterActorBox *box)
{
g_return_val_if_fail (box != NULL, 0.);
return box->x2 - box->x1;
}
/**
* clutter_actor_box_get_height:
* @box: a #ClutterActorBox
*
* Retrieves the height of the @box
*
* Return value: the height of the box
*/
gfloat
clutter_actor_box_get_height (const ClutterActorBox *box)
{
g_return_val_if_fail (box != NULL, 0.);
return box->y2 - box->y1;
}
/**
* clutter_actor_box_get_origin:
* @box: a #ClutterActorBox
* @x: (out) (allow-none): return location for the X coordinate, or %NULL
* @y: (out) (allow-none): return location for the Y coordinate, or %NULL
*
* Retrieves the origin of @box
*/
void
clutter_actor_box_get_origin (const ClutterActorBox *box,
gfloat *x,
gfloat *y)
{
g_return_if_fail (box != NULL);
if (x)
*x = box->x1;
if (y)
*y = box->y1;
}
/**
* clutter_actor_box_get_size:
* @box: a #ClutterActorBox
* @width: (out) (allow-none): return location for the width, or %NULL
* @height: (out) (allow-none): return location for the height, or %NULL
*
* Retrieves the size of @box
*/
void
clutter_actor_box_get_size (const ClutterActorBox *box,
gfloat *width,
gfloat *height)
{
g_return_if_fail (box != NULL);
if (width)
*width = box->x2 - box->x1;
if (height)
*height = box->y2 - box->y1;
}
/**
* clutter_actor_box_get_area:
* @box: a #ClutterActorBox
*
* Retrieves the area of @box
*
* Return value: the area of a #ClutterActorBox, in pixels
*/
gfloat
clutter_actor_box_get_area (const ClutterActorBox *box)
{
g_return_val_if_fail (box != NULL, 0.);
return (box->x2 - box->x1) * (box->y2 - box->y1);
}
/**
* clutter_actor_box_contains:
* @box: a #ClutterActorBox
* @x: X coordinate of the point
* @y: Y coordinate of the point
*
* Checks whether a point with @x, @y coordinates is contained
* within @box
*
* Return value: %TRUE if the point is contained by the #ClutterActorBox
*/
gboolean
clutter_actor_box_contains (const ClutterActorBox *box,
gfloat x,
gfloat y)
{
g_return_val_if_fail (box != NULL, FALSE);
return (x > box->x1 && x < box->x2) &&
(y > box->y1 && y < box->y2);
}
/**
* clutter_actor_box_from_vertices:
* @box: a #ClutterActorBox
* @verts: (array fixed-size=4): array of four #graphene_point3d_t
*
* Calculates the bounding box represented by the four vertices; for details
* of the vertex array see [method@Actor.get_abs_allocation_vertices].
*/
void
clutter_actor_box_from_vertices (ClutterActorBox *box,
const graphene_point3d_t verts[])
{
gfloat x_1, x_2, y_1, y_2;
g_return_if_fail (box != NULL);
g_return_if_fail (verts != NULL);
/* 4-way min/max */
x_1 = verts[0].x;
y_1 = verts[0].y;
if (verts[1].x < x_1)
x_1 = verts[1].x;
if (verts[2].x < x_1)
x_1 = verts[2].x;
if (verts[3].x < x_1)
x_1 = verts[3].x;
if (verts[1].y < y_1)
y_1 = verts[1].y;
if (verts[2].y < y_1)
y_1 = verts[2].y;
if (verts[3].y < y_1)
y_1 = verts[3].y;
x_2 = verts[0].x;
y_2 = verts[0].y;
if (verts[1].x > x_2)
x_2 = verts[1].x;
if (verts[2].x > x_2)
x_2 = verts[2].x;
if (verts[3].x > x_2)
x_2 = verts[3].x;
if (verts[1].y > y_2)
y_2 = verts[1].y;
if (verts[2].y > y_2)
y_2 = verts[2].y;
if (verts[3].y > y_2)
y_2 = verts[3].y;
box->x1 = x_1;
box->x2 = x_2;
box->y1 = y_1;
box->y2 = y_2;
}
/**
* clutter_actor_box_interpolate:
* @initial: the initial #ClutterActorBox
* @final: the final #ClutterActorBox
* @progress: the interpolation progress
* @result: (out): return location for the interpolation
*
* Interpolates between @initial and @final `ClutterActorBox`es
* using @progress
*/
void
clutter_actor_box_interpolate (const ClutterActorBox *initial,
const ClutterActorBox *final,
gdouble progress,
ClutterActorBox *result)
{
g_return_if_fail (initial != NULL);
g_return_if_fail (final != NULL);
g_return_if_fail (result != NULL);
result->x1 = initial->x1 + (final->x1 - initial->x1) * progress;
result->y1 = initial->y1 + (final->y1 - initial->y1) * progress;
result->x2 = initial->x2 + (final->x2 - initial->x2) * progress;
result->y2 = initial->y2 + (final->y2 - initial->y2) * progress;
}
/**
* clutter_actor_box_clamp_to_pixel:
* @box: (inout): the #ClutterActorBox to clamp
*
* Clamps the components of @box to the nearest integer
*/
void
clutter_actor_box_clamp_to_pixel (ClutterActorBox *box)
{
g_return_if_fail (box != NULL);
box->x1 = floorf (box->x1);
box->y1 = floorf (box->y1);
box->x2 = ceilf (box->x2);
box->y2 = ceilf (box->y2);
}
/**
* clutter_actor_box_union:
2013-12-04 16:09:09 +00:00
* @a: (in): the first #ClutterActorBox
* @b: (in): the second #ClutterActorBox
* @result: (out): the #ClutterActorBox representing a union
* of @a and @b
*
* Unions the two boxes @a and @b and stores the result in @result.
*/
void
clutter_actor_box_union (const ClutterActorBox *a,
const ClutterActorBox *b,
ClutterActorBox *result)
{
g_return_if_fail (a != NULL);
g_return_if_fail (b != NULL);
g_return_if_fail (result != NULL);
result->x1 = MIN (a->x1, b->x1);
result->y1 = MIN (a->y1, b->y1);
result->x2 = MAX (a->x2, b->x2);
result->y2 = MAX (a->y2, b->y2);
}
static gboolean
clutter_actor_box_progress (const GValue *a,
const GValue *b,
gdouble factor,
GValue *retval)
{
ClutterActorBox res = { 0, };
clutter_actor_box_interpolate (g_value_get_boxed (a),
g_value_get_boxed (b),
factor,
&res);
g_value_set_boxed (retval, &res);
return TRUE;
}
/**
* clutter_actor_box_set_origin:
* @box: a #ClutterActorBox
* @x: the X coordinate of the new origin
* @y: the Y coordinate of the new origin
*
* Changes the origin of @box, maintaining the size of the #ClutterActorBox.
*/
void
clutter_actor_box_set_origin (ClutterActorBox *box,
gfloat x,
gfloat y)
{
gfloat width, height;
g_return_if_fail (box != NULL);
width = box->x2 - box->x1;
height = box->y2 - box->y1;
clutter_actor_box_init_rect (box, x, y, width, height);
}
/**
* clutter_actor_box_set_size:
* @box: a #ClutterActorBox
* @width: the new width
* @height: the new height
*
* Sets the size of @box, maintaining the origin of the #ClutterActorBox.
*/
void
clutter_actor_box_set_size (ClutterActorBox *box,
gfloat width,
gfloat height)
{
g_return_if_fail (box != NULL);
box->x2 = box->x1 + width;
box->y2 = box->y1 + height;
}
clutter: Fix offscreen-effect painting of clones `ClutterOffscreenEffect` had been getting the wrong bounding box in the case of clones and descendents of clones, causing visibly incorrect clipping. This was due to `clutter_actor_get_paint_box` only ever being given the source actor during a paint (which is correct) and not the clone. Even if we weren't painting a clone but an offscreened descendent of a clone (like in gnome-shell's desktop zoom), we would get the wrong result. Fortunately we don't need to know the actual clone/actor being painted so don't need to call the problematic `clutter_actor_get_paint_box` at all. The solution is to only keep untransformed rendering in the FBO and leave the correct transformation for later. The correct clone/actor's transformation is already set for us as the current cogl modelview matrix by `clutter_actor_paint`. Bonus optimization: This all means we don't need to keep `last_matrix_drawn` or force a full repaint every time some part of the transformation changes. Because the FBO contents are no longer affected by transformations. As it should be. In other words, offscreen-effected actors can now move around on screen without themselves being repainted. Special thanks to Mai Lavelle for identifying the cause of the problem. Fixes: https://bugzilla.gnome.org/show_bug.cgi?id=789050, https://bugzilla.gnome.org/show_bug.cgi?id=659523#c9, https://gitlab.gnome.org/GNOME/mutter/issues/196, https://gitlab.gnome.org/GNOME/mutter/issues/282, https://gitlab.gnome.org/GNOME/gnome-shell/issues/387, https://launchpad.net/bugs/1767648, https://launchpad.net/bugs/1779615
2018-05-24 09:51:22 +00:00
void
_clutter_actor_box_enlarge_for_effects (ClutterActorBox *box)
{
float width, height;
/* The aim here is that for a given rectangle defined with floating point
* coordinates we want to determine a stable quantized size in pixels
* that doesn't vary due to the original box's sub-pixel position.
*
* The reason this is important is because effects will use this
* API to determine the size of offscreen framebuffers and so for
* a fixed-size object that may be animated across the screen we
clutter: Fix offscreen-effect painting of clones `ClutterOffscreenEffect` had been getting the wrong bounding box in the case of clones and descendents of clones, causing visibly incorrect clipping. This was due to `clutter_actor_get_paint_box` only ever being given the source actor during a paint (which is correct) and not the clone. Even if we weren't painting a clone but an offscreened descendent of a clone (like in gnome-shell's desktop zoom), we would get the wrong result. Fortunately we don't need to know the actual clone/actor being painted so don't need to call the problematic `clutter_actor_get_paint_box` at all. The solution is to only keep untransformed rendering in the FBO and leave the correct transformation for later. The correct clone/actor's transformation is already set for us as the current cogl modelview matrix by `clutter_actor_paint`. Bonus optimization: This all means we don't need to keep `last_matrix_drawn` or force a full repaint every time some part of the transformation changes. Because the FBO contents are no longer affected by transformations. As it should be. In other words, offscreen-effected actors can now move around on screen without themselves being repainted. Special thanks to Mai Lavelle for identifying the cause of the problem. Fixes: https://bugzilla.gnome.org/show_bug.cgi?id=789050, https://bugzilla.gnome.org/show_bug.cgi?id=659523#c9, https://gitlab.gnome.org/GNOME/mutter/issues/196, https://gitlab.gnome.org/GNOME/mutter/issues/282, https://gitlab.gnome.org/GNOME/gnome-shell/issues/387, https://launchpad.net/bugs/1767648, https://launchpad.net/bugs/1779615
2018-05-24 09:51:22 +00:00
* want to make sure that the stage paint-box has an equally stable
* size so that effects aren't made to continuously re-allocate
* a corresponding fbo.
*
* The other thing we consider is that the calculation of this box is
* subject to floating point precision issues that might be slightly
* different to the precision issues involved with actually painting the
* actor, which might result in painting slightly leaking outside the
* user's calculated paint-volume. For this we simply aim to pad out the
* paint-volume by at least half a pixel all the way around.
*/
width = box->x2 - box->x1;
height = box->y2 - box->y1;
width = CLUTTER_NEARBYINT (width);
height = CLUTTER_NEARBYINT (height);
/* XXX: NB the width/height may now be up to 0.5px too small so we
* must also pad by 0.25px all around to account for this. In total we
* must padd by at least 0.75px around all sides. */
/* XXX: The furthest that we can overshoot the bottom right corner by
* here is 1.75px in total if you consider that the 0.75 padding could
* just cross an integer boundary and so ceil will effectively add 1.
*/
box->x2 = ceilf (box->x2 + 0.75);
box->y2 = ceilf (box->y2 + 0.75);
/* Now we redefine the top-left relative to the bottom right based on the
* rounded width/height determined above + a constant so that the overall
* size of the box will be stable and not dependent on the box's
clutter: Fix offscreen-effect painting of clones `ClutterOffscreenEffect` had been getting the wrong bounding box in the case of clones and descendents of clones, causing visibly incorrect clipping. This was due to `clutter_actor_get_paint_box` only ever being given the source actor during a paint (which is correct) and not the clone. Even if we weren't painting a clone but an offscreened descendent of a clone (like in gnome-shell's desktop zoom), we would get the wrong result. Fortunately we don't need to know the actual clone/actor being painted so don't need to call the problematic `clutter_actor_get_paint_box` at all. The solution is to only keep untransformed rendering in the FBO and leave the correct transformation for later. The correct clone/actor's transformation is already set for us as the current cogl modelview matrix by `clutter_actor_paint`. Bonus optimization: This all means we don't need to keep `last_matrix_drawn` or force a full repaint every time some part of the transformation changes. Because the FBO contents are no longer affected by transformations. As it should be. In other words, offscreen-effected actors can now move around on screen without themselves being repainted. Special thanks to Mai Lavelle for identifying the cause of the problem. Fixes: https://bugzilla.gnome.org/show_bug.cgi?id=789050, https://bugzilla.gnome.org/show_bug.cgi?id=659523#c9, https://gitlab.gnome.org/GNOME/mutter/issues/196, https://gitlab.gnome.org/GNOME/mutter/issues/282, https://gitlab.gnome.org/GNOME/gnome-shell/issues/387, https://launchpad.net/bugs/1767648, https://launchpad.net/bugs/1779615
2018-05-24 09:51:22 +00:00
* position.
*
* Adding 3px to the width/height will ensure we cover the maximum of
* 1.75px padding on the bottom/right and still ensure we have > 0.75px
* padding on the top/left.
*/
box->x1 = box->x2 - width - 3;
box->y1 = box->y2 - height - 3;
}
/**
* clutter_actor_box_scale:
* @box: a #ClutterActorBox
* @scale: scale factor for resizing this box
*
* Rescale the @box by provided @scale factor.
*/
void
clutter_actor_box_scale (ClutterActorBox *box,
gfloat scale)
{
g_return_if_fail (box != NULL);
box->x1 *= scale;
box->x2 *= scale;
box->y1 *= scale;
box->y2 *= scale;
}
/**
* clutter_actor_box_is_initialized:
* @box: a #ClutterActorBox
*
* Checks if @box has been initialized, a #ClutterActorBox is uninitialized
* if it has a size of -1 at an origin of 0, 0.
*
* Returns: %TRUE if the box is uninitialized, %FALSE if it isn't
*/
gboolean
clutter_actor_box_is_initialized (ClutterActorBox *box)
{
gboolean x1_uninitialized, x2_uninitialized;
gboolean y1_uninitialized, y2_uninitialized;
g_return_val_if_fail (box != NULL, TRUE);
x1_uninitialized = isinf (box->x1);
x2_uninitialized = isinf (box->x2) && signbit (box->x2);
y1_uninitialized = isinf (box->y1);
y2_uninitialized = isinf (box->y2) && signbit (box->y2);
return !x1_uninitialized || !x2_uninitialized ||
!y1_uninitialized || !y2_uninitialized;
}
G_DEFINE_BOXED_TYPE_WITH_CODE (ClutterActorBox, clutter_actor_box,
clutter_actor_box_copy,
clutter_actor_box_free,
CLUTTER_REGISTER_INTERVAL_PROGRESS (clutter_actor_box_progress));