/* GSK - The GTK Scene Kit
*
* Copyright 2016 Endless
*
* 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/>.
*/
/**
* GskRoundedRect :
* @ bounds : the bounds of the rectangle
* @ corner : the size of the 4 rounded corners
*
* A rectangular region with rounded corners .
*
* Application code should normalize rectangles using
* [ method @ Gsk . RoundedRect . normalize ] ; this function will ensure that
* the bounds of the rectangle are normalized and ensure that the corner
* values are positive and the corners do not overlap .
*
* All functions taking a ` GskRoundedRect ` as an argument will internally
* operate on a normalized copy ; all functions returning a ` GskRoundedRect `
* will always return a normalized one .
*
* The algorithm used for normalizing corner sizes is described in
* [ the CSS specification ] ( https : //drafts.csswg.org/css-backgrounds-3/#border-radius).
*/
#include "config.h"
#include "gskroundedrect.h"
#include "gskroundedrectprivate.h"
#include "gskdebugprivate.h"
#include "gskrectprivate.h"
#include <math.h>
static float
gsk_rounded_rect_get_corner_scale_factor (GskRoundedRect *self)
{
float factor = 1 .0 ;
float corners;
corners = self->corner[GSK_CORNER_TOP_LEFT].width + self->corner[GSK_CORNER_TOP_RIGHT].width;
if (corners > self->bounds.size.width)
factor = MIN (factor, self->bounds.size.width / corners);
corners = self->corner[GSK_CORNER_TOP_RIGHT].height + self->corner[GSK_CORNER_BOTTOM_RIGHT].height;
if (corners > self->bounds.size.height)
factor = MIN (factor, self->bounds.size.height / corners);
corners = self->corner[GSK_CORNER_BOTTOM_RIGHT].width + self->corner[GSK_CORNER_BOTTOM_LEFT].width;
if (corners > self->bounds.size.width)
factor = MIN (factor, self->bounds.size.width / corners);
corners = self->corner[GSK_CORNER_TOP_LEFT].height + self->corner[GSK_CORNER_BOTTOM_LEFT].height;
if (corners > self->bounds.size.height)
factor = MIN (factor, self->bounds.size.height / corners);
return factor;
}
static void
gsk_rounded_rect_normalize_in_place (GskRoundedRect *self)
{
float factor;
guint i;
graphene_rect_normalize (&self->bounds);
for (i = 0 ; i < 4 ; i++)
{
self->corner[i].width = MAX (self->corner[i].width, 0 );
self->corner[i].height = MAX (self->corner[i].height, 0 );
}
/* clamp border radius, following CSS specs */
factor = gsk_rounded_rect_get_corner_scale_factor (self);
for (i = 0 ; i < 4 ; i++)
graphene_size_scale (&self->corner[i], factor, &self->corner[i]);
}
/**
* gsk_rounded_rect_init :
* @ self : the rounded rectangle to initialize
* @ bounds : a ` graphene_rect_t ` describing the bounds
* @ top_left : the rounding radius of the top left corner
* @ top_right : the rounding radius of the top right corner
* @ bottom_right : the rounding radius of the bottom right corner
* @ bottom_left : the rounding radius of the bottom left corner
*
* Initializes a rounded rectangle with the given values .
*
* This function will implicitly normalize the rounded rectangle
* before returning .
*
* Returns : ( transfer none ) : the initialized rounded rectangle
*/
GskRoundedRect *
gsk_rounded_rect_init (GskRoundedRect *self,
const graphene_rect_t *bounds,
const graphene_size_t *top_left,
const graphene_size_t *top_right,
const graphene_size_t *bottom_right,
const graphene_size_t *bottom_left)
{
graphene_rect_init_from_rect (&self->bounds, bounds);
graphene_size_init_from_size (&self->corner[GSK_CORNER_TOP_LEFT], top_left);
graphene_size_init_from_size (&self->corner[GSK_CORNER_TOP_RIGHT], top_right);
graphene_size_init_from_size (&self->corner[GSK_CORNER_BOTTOM_RIGHT], bottom_right);
graphene_size_init_from_size (&self->corner[GSK_CORNER_BOTTOM_LEFT], bottom_left);
gsk_rounded_rect_normalize_in_place (self);
return self;
}
/**
* gsk_rounded_rect_init_copy :
* @ self : the rounded rectangle to initialize
* @ src : another rounded rectangle
*
* Initializes a rounded rectangle with a copy .
*
* This function will not normalize the rounded rectangle ,
* so make sure the source is normalized .
*
* Returns : ( transfer none ) : the initialized rounded rectangle
*/
GskRoundedRect *
gsk_rounded_rect_init_copy (GskRoundedRect *self,
const GskRoundedRect *src)
{
*self = *src;
return self;
}
/**
* gsk_rounded_rect_init_from_rect :
* @ self : the rounded rectangle to initialize
* @ bounds : a ` graphene_rect_t `
* @ radius : the border radius
*
* Initializes a rounded rectangle to the given bounds
* and sets the radius of all four corners equally .
*
* Returns : ( transfer none ) : the initialized rounded rectangle
**/
GskRoundedRect *
gsk_rounded_rect_init_from_rect (GskRoundedRect *self,
const graphene_rect_t *bounds,
float radius)
{
graphene_size_t corner = GRAPHENE_SIZE_INIT(radius, radius);
return gsk_rounded_rect_init (self, bounds, &corner, &corner, &corner, &corner);
}
/**
* gsk_rounded_rect_normalize :
* @ self : a rounded rectangle
*
* Normalizes a rounded rectangle .
*
* This function will ensure that the bounds of the rounded rectangle
* are normalized and ensure that the corner values are positive
* and the corners do not overlap .
*
* Returns : ( transfer none ) : the normalized rounded rectangle
*/
GskRoundedRect *
gsk_rounded_rect_normalize (GskRoundedRect *self)
{
gsk_rounded_rect_normalize_in_place (self);
return self;
}
/**
* gsk_rounded_rect_offset :
* @ self : a rounded rectangle
* @ dx : the horizontal offset
* @ dy : the vertical offset
*
* Offsets the rounded rectangle ' s origin by @ dx and @ dy .
*
* The size and corners of the rounded rectangle are unchanged .
*
* Returns : ( transfer none ) : the offset rounded rectangle
*/
GskRoundedRect *
gsk_rounded_rect_offset (GskRoundedRect *self,
float dx,
float dy)
{
gsk_rounded_rect_normalize (self);
self->bounds.origin.x += dx;
self->bounds.origin.y += dy;
return self;
}
static inline void
border_radius_shrink (graphene_size_t *corner,
double width,
double height,
const graphene_size_t *max)
{
if (corner->width > 0 )
corner->width -= width;
if (corner->height > 0 )
corner->height -= height;
if (corner->width <= 0 || corner->height <= 0 )
{
corner->width = 0 ;
corner->height = 0 ;
}
else
{
corner->width = MIN (corner->width, max->width);
corner->height = MIN (corner->height, max->height);
}
}
/**
* gsk_rounded_rect_shrink :
* @ self : the rounded rectangle to shrink or grow
* @ top : how far to move the top side downwards
* @ right : how far to move the right side to the left
* @ bottom : how far to move the bottom side upwards
* @ left : how far to move the left side to the right
*
* Shrinks ( or grows ) a rounded rectangle by moving the 4 sides
* according to the offsets given .
*
* The corner radii will be changed in a way that tries to keep
* the center of the corner circle intact . This emulates CSS behavior .
*
* This function also works for growing rounded rectangles
* if you pass negative values for the @ top , @ right , @ bottom or @ left .
*
* Returns : ( transfer none ) : the resized rounded rectangle
*/
GskRoundedRect *
gsk_rounded_rect_shrink (GskRoundedRect *self,
float top,
float right,
float bottom,
float left)
{
float width = left + right;
float height = top + bottom;
if (self->bounds.size.width - width < 0 )
{
self->bounds.origin.x += left * self->bounds.size.width / width;
self->bounds.size.width = 0 ;
}
else
{
self->bounds.origin.x += left;
self->bounds.size.width -= width;
}
if (self->bounds.size.height - height < 0 )
{
self->bounds.origin.y += top * self->bounds.size.height / height;
self->bounds.size.height = 0 ;
}
else
{
self->bounds.origin.y += top;
self->bounds.size.height -= height;
}
border_radius_shrink (&self->corner[GSK_CORNER_TOP_LEFT], left, top, &self->bounds.size);
border_radius_shrink (&self->corner[GSK_CORNER_TOP_RIGHT], right, top, &self->bounds.size);
border_radius_shrink (&self->corner[GSK_CORNER_BOTTOM_RIGHT], right, bottom, &self->bounds.size);
border_radius_shrink (&self->corner[GSK_CORNER_BOTTOM_LEFT], left, bottom, &self->bounds.size);
return self;
}
void
gsk_rounded_rect_scale_affine (GskRoundedRect *dest,
const GskRoundedRect *src,
float scale_x,
float scale_y,
float dx,
float dy)
{
guint flip = ((scale_x < 0 ) ? 1 : 0 ) + (scale_y < 0 ? 2 : 0 );
g_assert (dest != src);
gsk_rect_scale (&src->bounds, scale_x, scale_y, &dest->bounds);
gsk_rect_init_offset (&dest->bounds, &dest->bounds, &GRAPHENE_POINT_INIT (dx, dy));
scale_x = fabsf (scale_x);
scale_y = fabsf (scale_y);
for (guint i = 0 ; i < 4 ; i++)
{
dest->corner[i].width = src->corner[i ^ flip].width * scale_x;
dest->corner[i].height = src->corner[i ^ flip].height * scale_y;
}
}
/* The permutation of corners that is induced
* by the dihedral transform .
*/
static GskCorner
gsk_corner_dihedral (GskCorner corner,
GdkDihedral dihedral)
{
static const GskCorner p[8 ][4 ] = {
[GDK_DIHEDRAL_NORMAL] = { 0 , 1 , 2 , 3 },
[GDK_DIHEDRAL_90] = { 3 , 0 , 1 , 2 },
[GDK_DIHEDRAL_180] = { 2 , 3 , 0 , 1 },
[GDK_DIHEDRAL_270] = { 1 , 2 , 3 , 0 },
[GDK_DIHEDRAL_FLIPPED] = { 1 , 0 , 3 , 2 },
[GDK_DIHEDRAL_FLIPPED_90] = { 0 , 3 , 2 , 1 },
[GDK_DIHEDRAL_FLIPPED_180] = { 3 , 2 , 1 , 0 },
[GDK_DIHEDRAL_FLIPPED_270] = { 2 , 1 , 0 , 3 },
};
return p[dihedral][corner];
}
void
gsk_rounded_rect_dihedral (GskRoundedRect *dest,
const GskRoundedRect *src,
GdkDihedral dihedral)
{
gsk_rect_dihedral (&src->bounds, dihedral, &dest->bounds);
if (gdk_dihedral_swaps_xy (dihedral))
{
for (guint i = 0 ; i < 4 ; i++)
{
GskCorner c = gsk_corner_dihedral ((GskCorner)i, dihedral);
dest->corner[i].width = src->corner[c].height;
dest->corner[i].height = src->corner[c].width;
}
}
else
{
for (guint i = 0 ; i < 4 ; i++)
{
GskCorner c = gsk_corner_dihedral ((GskCorner)i, dihedral);
dest->corner[i].width = src->corner[c].width;
dest->corner[i].height = src->corner[c].height;
}
}
}
/*<private>
* gsk_rounded_rect_is_circular :
* @ self : the rounded rectangle to check
*
* Checks if all corners of a rounded rectangle are quarter - circles
* ( as opposed to quarter - ellipses ) .
*
* Note that different corners can still have different radii .
*
* Returns : true if the rounded rectangle is circular
*/
gboolean
gsk_rounded_rect_is_circular (const GskRoundedRect *self)
{
for (guint i = 0 ; i < 4 ; i++)
{
if (self->corner[i].width != self->corner[i].height)
return FALSE ;
}
return TRUE ;
}
/**
* gsk_rounded_rect_is_rectilinear :
* @ self : the rounded rectangle to check
*
* Checks if all corners of a rounded rectangle are right angles
* and the rectangle covers all of its bounds .
*
* This information can be used to decide if [ ctor @ Gsk . ClipNode . new ]
* or [ ctor @ Gsk . RoundedClipNode . new ] should be called .
*
* Returns : true if the rounded rectangle is rectilinear
**/
gboolean
gsk_rounded_rect_is_rectilinear (const GskRoundedRect *self)
{
for (guint i = 0 ; i < 4 ; i++)
{
if (self->corner[i].width > 0 ||
self->corner[i].height > 0 )
return FALSE ;
}
return TRUE ;
}
static inline gboolean
ellipsis_contains_point (const graphene_size_t *ellipsis,
const graphene_point_t *point)
{
return (point->x * point->x) / (ellipsis->width * ellipsis->width)
+ (point->y * point->y) / (ellipsis->height * ellipsis->height) <= 1 ;
}
typedef enum
{
INSIDE,
OUTSIDE_TOP_LEFT,
OUTSIDE_TOP_RIGHT,
OUTSIDE_BOTTOM_LEFT,
OUTSIDE_BOTTOM_RIGHT,
OUTSIDE
} Location;
static Location
gsk_rounded_rect_locate_point (const GskRoundedRect *self,
const graphene_point_t *point)
{
float px, py;
float ox, oy;
ox = self->bounds.origin.x + self->bounds.size.width;
oy = self->bounds.origin.y + self->bounds.size.height;
if (point->x < self->bounds.origin.x ||
point->y < self->bounds.origin.y ||
point->x > ox ||
point->y > oy)
return OUTSIDE;
px = self->bounds.origin.x + self->corner[GSK_CORNER_TOP_LEFT].width - point->x;
py = self->bounds.origin.y + self->corner[GSK_CORNER_TOP_LEFT].height - point->y;
if (px > 0 && py > 0 &&
!ellipsis_contains_point (&self->corner[GSK_CORNER_TOP_LEFT], &GRAPHENE_POINT_INIT (px, py)))
return OUTSIDE_TOP_LEFT;
px = ox - self->corner[GSK_CORNER_TOP_RIGHT].width - point->x;
py = self->bounds.origin.y + self->corner[GSK_CORNER_TOP_RIGHT].height - point->y;
if (px < 0 && py > 0 &&
!ellipsis_contains_point (&self->corner[GSK_CORNER_TOP_RIGHT], &GRAPHENE_POINT_INIT (px, py)))
return OUTSIDE_TOP_RIGHT;
px = self->bounds.origin.x + self->corner[GSK_CORNER_BOTTOM_LEFT].width - point->x;
py = oy - self->corner[GSK_CORNER_BOTTOM_LEFT].height - point->y;
if (px > 0 && py < 0 &&
!ellipsis_contains_point (&self->corner[GSK_CORNER_BOTTOM_LEFT],
&GRAPHENE_POINT_INIT (px, py)))
return OUTSIDE_BOTTOM_LEFT;
px = ox - self->corner[GSK_CORNER_BOTTOM_RIGHT].width - point->x;
py = oy - self->corner[GSK_CORNER_BOTTOM_RIGHT].height - point->y;
if (px < 0 && py < 0 &&
!ellipsis_contains_point (&self->corner[GSK_CORNER_BOTTOM_RIGHT],
&GRAPHENE_POINT_INIT (px, py)))
return OUTSIDE_BOTTOM_RIGHT;
return INSIDE;
}
/**
* gsk_rounded_rect_contains_point :
* @ self : a rounded rectangle
* @ point : the point to check
*
* Checks if the given point is inside the rounded rectangle .
*
* Returns : true if the point is inside the rounded rectangle
**/
gboolean
gsk_rounded_rect_contains_point (const GskRoundedRect *self,
const graphene_point_t *point)
{
return gsk_rounded_rect_locate_point (self, point) == INSIDE;
}
/**
* gsk_rounded_rect_contains_rect :
* @ self : a rounded rectangle
* @ rect : the rectangle to check
*
* Checks if the given rectangle is contained inside the rounded rectangle .
*
* Returns : true if the @ rect is fully contained inside the rounded rectangle
**/
gboolean
gsk_rounded_rect_contains_rect (const GskRoundedRect *self,
const graphene_rect_t *rect)
{
float tx, ty;
float px, py;
float ox, oy;
tx = rect->origin.x + rect->size.width;
ty = rect->origin.y + rect->size.height;
ox = self->bounds.origin.x + self->bounds.size.width;
oy = self->bounds.origin.y + self->bounds.size.height;
if (rect->origin.x < self->bounds.origin.x ||
rect->origin.y < self->bounds.origin.y ||
tx > ox ||
ty > oy)
return FALSE ;
px = self->bounds.origin.x + self->corner[GSK_CORNER_TOP_LEFT].width - rect->origin.x;
py = self->bounds.origin.y + self->corner[GSK_CORNER_TOP_LEFT].height - rect->origin.y;
if (px > 0 && py > 0 &&
!ellipsis_contains_point (&self->corner[GSK_CORNER_TOP_LEFT], &GRAPHENE_POINT_INIT (px, py)))
return FALSE ;
px = ox - self->corner[GSK_CORNER_TOP_RIGHT].width - tx;
py = self->bounds.origin.y + self->corner[GSK_CORNER_TOP_RIGHT].height - rect->origin.y;
if (px < 0 && py > 0 &&
!ellipsis_contains_point (&self->corner[GSK_CORNER_TOP_RIGHT], &GRAPHENE_POINT_INIT (px, py)))
return FALSE ;
px = self->bounds.origin.x + self->corner[GSK_CORNER_BOTTOM_LEFT].width - rect->origin.x;
py = oy - self->corner[GSK_CORNER_BOTTOM_LEFT].height - ty;
if (px > 0 && py < 0 &&
!ellipsis_contains_point (&self->corner[GSK_CORNER_BOTTOM_LEFT],
&GRAPHENE_POINT_INIT (px, py)))
return FALSE ;
px = ox - self->corner[GSK_CORNER_BOTTOM_RIGHT].width - tx;
py = oy - self->corner[GSK_CORNER_BOTTOM_RIGHT].height - ty;
if (px < 0 && py < 0 &&
!ellipsis_contains_point (&self->corner[GSK_CORNER_BOTTOM_RIGHT],
&GRAPHENE_POINT_INIT (px, py)))
return FALSE ;
return TRUE ;
}
/**
* gsk_rounded_rect_intersects_rect :
* @ self : a rounded rectangle
* @ rect : the rectangle to check
*
* Checks if part a rectangle is contained
* inside the rounded rectangle .
*
* Returns : true if the @ rect intersects with the rounded rectangle
*/
gboolean
gsk_rounded_rect_intersects_rect (const GskRoundedRect *self,
const graphene_rect_t *rect)
{
if (!gsk_rect_intersects (&self->bounds, rect))
return FALSE ;
/* If the bounding boxes intersect but the rectangles don't,
* one of the rect ' s corners must be in the opposite corner ' s
* outside region
*/
if (gsk_rounded_rect_locate_point (self, &rect->origin) == OUTSIDE_BOTTOM_RIGHT ||
gsk_rounded_rect_locate_point (self, &GRAPHENE_POINT_INIT (rect->origin.x + rect->size.width, rect->origin.y)) == OUTSIDE_BOTTOM_LEFT ||
gsk_rounded_rect_locate_point (self, &GRAPHENE_POINT_INIT (rect->origin.x, rect->origin.y + rect->size.height)) == OUTSIDE_TOP_RIGHT ||
gsk_rounded_rect_locate_point (self, &GRAPHENE_POINT_INIT (rect->origin.x + rect->size.width, rect->origin.y + rect->size.height)) == OUTSIDE_TOP_LEFT)
return FALSE ;
return TRUE ;
}
#define rect_point0(r) ((r)->origin)
#define rect_point1(r) (GRAPHENE_POINT_INIT ((r)->origin.x + (r)->size.width, (r)->origin.y))
#define rect_point2(r) (GRAPHENE_POINT_INIT ((r)->origin.x + (r)->size.width, (r)->origin.y + (r)->size.height))
#define rect_point3(r) (GRAPHENE_POINT_INIT ((r)->origin.x, (r)->origin.y + (r)->size.height))
#define rounded_rect_corner0(r) \
(GRAPHENE_RECT_INIT((r)->bounds.origin.x, \
(r)->bounds.origin.y, \
(r)->corner[0 ].width, (r)->corner[0 ].height))
#define rounded_rect_corner1(r) \
(GRAPHENE_RECT_INIT((r)->bounds.origin.x + (r)->bounds.size.width - (r)->corner[1 ].width, \
(r)->bounds.origin.y, \
(r)->corner[1 ].width, (r)->corner[1 ].height))
#define rounded_rect_corner2(r) \
(GRAPHENE_RECT_INIT((r)->bounds.origin.x + (r)->bounds.size.width - (r)->corner[2 ].width, \
(r)->bounds.origin.y + (r)->bounds.size.height - (r)->corner[2 ].height, \
(r)->corner[2 ].width, (r)->corner[2 ].height))
#define rounded_rect_corner3(r) \
(GRAPHENE_RECT_INIT((r)->bounds.origin.x, \
(r)->bounds.origin.y + (r)->bounds.size.height - (r)->corner[3 ].height, \
(r)->corner[3 ].width, (r)->corner[3 ].height))
enum {
BELOW,
INNER,
ABOVE
};
static inline void
classify_point (const graphene_point_t *p, const graphene_rect_t *rect, int *px, int *py)
{
if (p->x <= rect->origin.x)
*px = BELOW;
else if (p->x >= rect->origin.x + rect->size.width)
*px = ABOVE;
else
*px = INNER;
if (p->y <= rect->origin.y)
*py = BELOW;
else if (p->y >= rect->origin.y + rect->size.height)
*py = ABOVE;
else
*py = INNER;
}
GskRoundedRectIntersection
gsk_rounded_rect_intersect_with_rect (const GskRoundedRect *self,
const graphene_rect_t *rect,
GskRoundedRect *result)
{
int px, py, qx, qy, rx, ry;
if (!gsk_rect_intersection (&self->bounds, rect, &result->bounds))
return GSK_INTERSECTION_EMPTY;
classify_point (&rect_point0 (rect), &rounded_rect_corner0 (self), &px, &py);
if (px == BELOW && py == BELOW)
{
classify_point (&rect_point2 (rect), &rounded_rect_corner0 (self), &qx, &qy);
if (qx == BELOW || qy == BELOW)
return GSK_INTERSECTION_EMPTY;
else if (qx == INNER && qy == INNER &&
gsk_rounded_rect_locate_point (self, &rect_point2 (rect)) != INSIDE)
{
classify_point (&rect_point2 (rect), &rounded_rect_corner2 (self), &rx, &ry);
if (rx == BELOW || ry == BELOW)
return GSK_INTERSECTION_EMPTY;
}
else if (qx == ABOVE && qy == ABOVE)
result->corner[0 ] = self->corner[0 ];
else
return GSK_INTERSECTION_NOT_REPRESENTABLE;
}
else if ((px == INNER || py == INNER) &&
gsk_rounded_rect_locate_point (self, &rect_point0 (rect)) != INSIDE)
{
if (gsk_rounded_rect_locate_point (self, &rect_point2 (rect)) == OUTSIDE_TOP_LEFT)
return GSK_INTERSECTION_EMPTY;
else
return GSK_INTERSECTION_NOT_REPRESENTABLE;
}
else
result->corner[0 ].width = result->corner[0 ].height = 0 ;
classify_point (&rect_point1 (rect), &rounded_rect_corner1 (self), &px, &py);
if (px == ABOVE && py == BELOW)
{
classify_point (&rect_point3 (rect), &rounded_rect_corner1 (self), &qx, &qy);
if (qx == ABOVE || qy == BELOW)
return GSK_INTERSECTION_EMPTY;
else if (qx == INNER && qy == INNER &&
gsk_rounded_rect_locate_point (self, &rect_point3 (rect)) != INSIDE)
{
classify_point (&rect_point3 (rect), &rounded_rect_corner3 (self), &rx, &ry);
if (rx == ABOVE || ry == BELOW)
return GSK_INTERSECTION_EMPTY;
}
else if (qx == BELOW && qy == ABOVE)
result->corner[1 ] = self->corner[1 ];
else
return GSK_INTERSECTION_NOT_REPRESENTABLE;
}
else if ((px == INNER || py == INNER) &&
gsk_rounded_rect_locate_point (self, &rect_point1 (rect)) != INSIDE)
{
if (gsk_rounded_rect_locate_point (self, &rect_point3 (rect)) == OUTSIDE_TOP_RIGHT)
return GSK_INTERSECTION_EMPTY;
else
return GSK_INTERSECTION_NOT_REPRESENTABLE;
}
else
result->corner[1 ].width = result->corner[1 ].height = 0 ;
classify_point (&rect_point2 (rect), &rounded_rect_corner2 (self), &px, &py);
if (px == ABOVE && py == ABOVE)
{
classify_point (&rect_point0 (rect), &rounded_rect_corner2 (self), &qx, &qy);
if (qx == ABOVE || qy == ABOVE)
return GSK_INTERSECTION_EMPTY;
else if (qx == INNER && qy == INNER &&
gsk_rounded_rect_locate_point (self, &rect_point0 (rect)) != INSIDE)
{
classify_point (&rect_point2 (rect), &rounded_rect_corner0 (self), &rx, &ry);
if (rx == ABOVE || ry == ABOVE)
return GSK_INTERSECTION_EMPTY;
}
else if (qx == BELOW && qy == BELOW)
result->corner[2 ] = self->corner[2 ];
else
return GSK_INTERSECTION_NOT_REPRESENTABLE;
}
else if ((px == INNER || py == INNER) &&
gsk_rounded_rect_locate_point (self, &rect_point2 (rect)) != INSIDE)
{
if (gsk_rounded_rect_locate_point (self, &rect_point0 (rect)) == OUTSIDE_BOTTOM_RIGHT)
return GSK_INTERSECTION_EMPTY;
else
return GSK_INTERSECTION_NOT_REPRESENTABLE;
}
else
result->corner[2 ].width = result->corner[2 ].height = 0 ;
classify_point (&rect_point3 (rect), &rounded_rect_corner3 (self), &px, &py);
if (px == BELOW && py == ABOVE)
{
classify_point (&rect_point1 (rect), &rounded_rect_corner3 (self), &qx, &qy);
if (qx == BELOW || qy == ABOVE)
return GSK_INTERSECTION_EMPTY;
else if (qx == INNER && qy == INNER &&
gsk_rounded_rect_locate_point (self, &rect_point1 (rect)) != INSIDE)
{
classify_point (&rect_point1 (rect), &rounded_rect_corner1 (self), &rx, &ry);
if (rx == BELOW || ry == ABOVE)
return GSK_INTERSECTION_EMPTY;
}
else if (qx == ABOVE && qy == BELOW)
result->corner[3 ] = self->corner[3 ];
else
return GSK_INTERSECTION_NOT_REPRESENTABLE;
}
else if ((px == INNER || py == INNER) &&
gsk_rounded_rect_locate_point (self, &rect_point3 (rect)) != INSIDE)
{
if (gsk_rounded_rect_locate_point (self, &rect_point1 (rect)) == OUTSIDE_BOTTOM_LEFT)
return GSK_INTERSECTION_EMPTY;
else
return GSK_INTERSECTION_NOT_REPRESENTABLE;
}
else
result->corner[3 ].width = result->corner[3 ].height = 0 ;
return GSK_INTERSECTION_NONEMPTY;
}
static gboolean
check_nonintersecting_corner (const GskRoundedRect *out,
const GskRoundedRect *in,
GskCorner corner,
float diff_x,
float diff_y,
GskRoundedRect *result)
{
g_assert (diff_x >= 0 );
g_assert (diff_y >= 0 );
if (out->corner[corner].width < diff_x ||
out->corner[corner].height < diff_y ||
(out->corner[corner].width <= in->corner[corner].width + diff_x &&
out->corner[corner].height <= in->corner[corner].height + diff_y))
{
result->corner[corner] = in->corner[corner];
return TRUE ;
}
if (diff_x > 0 || diff_y > 0 )
return FALSE ;
if (out->corner[corner].width > in->corner[corner].width &&
out->corner[corner].height > in->corner[corner].height)
{
result->corner[corner] = out->corner[corner];
return TRUE ;
}
return FALSE ;
}
/* a is outside in x direction, b is outside in y direction */
static gboolean
check_intersecting_corner (const GskRoundedRect *a,
const GskRoundedRect *b,
GskCorner corner,
float diff_x,
float diff_y,
GskRoundedRect *result)
{
g_assert (diff_x > 0 );
g_assert (diff_y > 0 );
if (diff_x < a->corner[corner].width ||
diff_x > a->bounds.size.width - a->corner[corner].width - a->corner[OPPOSITE_CORNER_X (corner)].width ||
diff_y < b->corner[corner].height ||
diff_y > b->bounds.size.height - b->corner[corner].height - b->corner[OPPOSITE_CORNER_Y (corner)].height)
return FALSE ;
result->corner[corner] = GRAPHENE_SIZE_INIT (0 , 0 );
return TRUE ;
}
static gboolean
check_corner (const GskRoundedRect *a,
const GskRoundedRect *b,
GskCorner corner,
float diff_x,
float diff_y,
GskRoundedRect *result)
{
if (diff_x >= 0 )
{
if (diff_y >= 0 )
{
return check_nonintersecting_corner (a, b, corner, diff_x, diff_y, result);
}
else if (diff_x == 0 )
{
return check_nonintersecting_corner (b, a, corner, 0 , - diff_y, result);
}
else
{
return check_intersecting_corner (a, b, corner, diff_x, - diff_y, result);
}
}
else
{
if (diff_y <= 0 )
{
return check_nonintersecting_corner (b, a, corner, - diff_x, - diff_y, result);
}
else
{
return check_intersecting_corner (b, a, corner, - diff_x, diff_y, result);
}
}
}
GskRoundedRectIntersection
gsk_rounded_rect_intersection (const GskRoundedRect *a,
const GskRoundedRect *b,
GskRoundedRect *result)
{
float top, left, bottom, right;
if (!gsk_rect_intersection (&a->bounds, &b->bounds, &result->bounds))
return GSK_INTERSECTION_EMPTY;
left = b->bounds.origin.x - a->bounds.origin.x;
top = b->bounds.origin.y - a->bounds.origin.y;
right = a->bounds.origin.x + a->bounds.size.width - b->bounds.origin.x - b->bounds.size.width;
bottom = a->bounds.origin.y + a->bounds.size.height - b->bounds.origin.y - b->bounds.size.height;
if (check_corner (a, b,
GSK_CORNER_TOP_LEFT,
left, top,
result) &&
check_corner (a, b,
GSK_CORNER_TOP_RIGHT,
right, top,
result) &&
check_corner (a, b,
GSK_CORNER_BOTTOM_LEFT,
left, bottom,
result) &&
check_corner (a, b,
GSK_CORNER_BOTTOM_RIGHT,
right, bottom,
result) &&
gsk_rounded_rect_get_corner_scale_factor (result) >= 1 .0 )
return GSK_INTERSECTION_NONEMPTY;
return GSK_INTERSECTION_NOT_REPRESENTABLE;
}
static void
append_arc (cairo_t *cr, double angle1, double angle2, gboolean negative)
{
if (negative)
cairo_arc_negative (cr, 0 .0 , 0 .0 , 1 .0 , angle1, angle2);
else
cairo_arc (cr, 0 .0 , 0 .0 , 1 .0 , angle1, angle2);
}
static void
_cairo_ellipsis (cairo_t *cr,
double xc, double yc,
double xradius, double yradius,
double angle1, double angle2)
{
cairo_matrix_t save;
if (xradius <= 0 .0 || yradius <= 0 .0 )
{
cairo_line_to (cr, xc, yc);
return ;
}
cairo_get_matrix (cr, &save);
cairo_translate (cr, xc, yc);
cairo_scale (cr, xradius, yradius);
append_arc (cr, angle1, angle2, FALSE );
cairo_set_matrix (cr, &save);
}
void
gsk_rounded_rect_path (const GskRoundedRect *self,
cairo_t *cr)
{
cairo_new_sub_path (cr);
_cairo_ellipsis (cr,
self->bounds.origin.x + self->corner[GSK_CORNER_TOP_LEFT].width,
self->bounds.origin.y + self->corner[GSK_CORNER_TOP_LEFT].height,
self->corner[GSK_CORNER_TOP_LEFT].width,
self->corner[GSK_CORNER_TOP_LEFT].height,
G_PI, 3 * G_PI_2);
_cairo_ellipsis (cr,
self->bounds.origin.x + self->bounds.size.width - self->corner[GSK_CORNER_TOP_RIGHT].width,
self->bounds.origin.y + self->corner[GSK_CORNER_TOP_RIGHT].height,
self->corner[GSK_CORNER_TOP_RIGHT].width,
self->corner[GSK_CORNER_TOP_RIGHT].height,
- G_PI_2, 0 );
_cairo_ellipsis (cr,
self->bounds.origin.x + self->bounds.size.width - self->corner[GSK_CORNER_BOTTOM_RIGHT].width,
self->bounds.origin.y + self->bounds.size.height - self->corner[GSK_CORNER_BOTTOM_RIGHT].height,
self->corner[GSK_CORNER_BOTTOM_RIGHT].width,
self->corner[GSK_CORNER_BOTTOM_RIGHT].height,
0 , G_PI_2);
_cairo_ellipsis (cr,
self->bounds.origin.x + self->corner[GSK_CORNER_BOTTOM_LEFT].width,
self->bounds.origin.y + self->bounds.size.height - self->corner[GSK_CORNER_BOTTOM_LEFT].height,
self->corner[GSK_CORNER_BOTTOM_LEFT].width,
self->corner[GSK_CORNER_BOTTOM_LEFT].height,
G_PI_2, G_PI);
cairo_close_path (cr);
}
/*< private >
* Converts to the format we use in our shaders :
* vec4 rect ;
* vec4 corner_widths ;
* vec4 corner_heights ;
* rect is ( x , y , width , height ) , the corners are the same
* order as in the rounded rect .
*
* This is so that shaders can use just the first vec4 for
* rectilinear rects , the 2 nd vec4 for circular rects and
* only look at the last vec4 if they have to .
*/
void
gsk_rounded_rect_to_float (const GskRoundedRect *self,
const graphene_point_t *offset,
float rect[12 ])
{
guint i;
rect[0 ] = self->bounds.origin.x + offset->x;
rect[1 ] = self->bounds.origin.y + offset->y;
rect[2 ] = self->bounds.size.width;
rect[3 ] = self->bounds.size.height;
for (i = 0 ; i < 4 ; i++)
{
rect[4 + i] = self->corner[i].width;
rect[8 + i] = self->corner[i].height;
}
}
static inline gboolean
gsk_size_equal (const graphene_size_t *s1,
const graphene_size_t *s2)
{
return s1->width == s2->width && s1->height == s2->height;
}
gboolean
gsk_rounded_rect_equal (gconstpointer rect1,
gconstpointer rect2)
{
const GskRoundedRect *self1 = rect1;
const GskRoundedRect *self2 = rect2;
return gsk_rect_equal (&self1->bounds, &self2->bounds)
&& gsk_size_equal (&self1->corner[0 ], &self2->corner[0 ])
&& gsk_size_equal (&self1->corner[1 ], &self2->corner[1 ])
&& gsk_size_equal (&self1->corner[2 ], &self2->corner[2 ])
&& gsk_size_equal (&self1->corner[3 ], &self2->corner[3 ]);
}
char *
gsk_rounded_rect_to_string (const GskRoundedRect *self)
{
return g_strdup_printf ("GskRoundedRect %p: Bounds: (%f, %f, %f, %f)"
" Corners: (%f, %f) (%f, %f) (%f, %f) (%f, %f)" ,
self,
self->bounds.origin.x,
self->bounds.origin.y,
self->bounds.size.width,
self->bounds.size.height,
self->corner[0 ].width,
self->corner[0 ].height,
self->corner[1 ].width,
self->corner[1 ].height,
self->corner[2 ].width,
self->corner[2 ].height,
self->corner[3 ].width,
self->corner[3 ].height);
}
/*< private >
* gsk_rounded_rect_get_largest_cover :
* @ self : the rounded rectangle to intersect with
* @ rect : the rectangle to intersect
* @ result : ( out caller - allocates ) : The resulting rectangle
*
* Computes the largest rectangle that is fully covered by both
* the given rect and the rounded rectangle .
*
* In particular , this function respects corners , so
*
* gsk_rounded_rect_get_largest_cover ( self , & self - > bounds , & rect )
*
* can be used to compute a decomposition for
* the rounded rectangle itself .
*/
gboolean
gsk_rounded_rect_get_largest_cover (const GskRoundedRect *self,
const graphene_rect_t *rect,
graphene_rect_t *result)
{
graphene_rect_t wide, high;
double start, end;
gboolean empty_wide, empty_high;
wide = self->bounds;
start = MAX(self->corner[GSK_CORNER_TOP_LEFT].height, self->corner[GSK_CORNER_TOP_RIGHT].height);
end = MAX(self->corner[GSK_CORNER_BOTTOM_LEFT].height, self->corner[GSK_CORNER_BOTTOM_RIGHT].height);
wide.size.height -= MIN (wide.size.height, start + end);
wide.origin.y += start;
empty_wide = !gsk_rect_intersection (&wide, rect, &wide);
high = self->bounds;
start = MAX(self->corner[GSK_CORNER_TOP_LEFT].width, self->corner[GSK_CORNER_BOTTOM_LEFT].width);
end = MAX(self->corner[GSK_CORNER_TOP_RIGHT].width, self->corner[GSK_CORNER_BOTTOM_RIGHT].width);
high.size.width -= MIN (high.size.width, start + end);
high.origin.x += start;
empty_high = !gsk_rect_intersection (&high, rect, &high);
if (empty_wide && empty_high)
return FALSE ;
else if (empty_wide)
*result = high;
else if (empty_high)
*result = wide;
else if (wide.size.width * wide.size.height > high.size.width * high.size.height)
*result = wide;
else
*result = high;
return TRUE ;
}
/*< private >
* gsk_rounded_rect_corner_box_contains_point :
* @ self : a rounded rectangle
* @ corner : the corner
* @ point : the point
*
* Returns whether @ point is inside the rectangle defining
* the quarter ellipses of the given corner .
*
* Returns : true if @ point is inside the @ corner ' s box
*/
gboolean
gsk_rounded_rect_corner_box_contains_point (const GskRoundedRect *self,
GskCorner corner,
const graphene_point_t *point)
{
graphene_rect_t rect;
graphene_size_init_from_size (&rect.size, &self->corner[corner]);
switch (corner)
{
case GSK_CORNER_TOP_LEFT:
rect.origin.x = self->bounds.origin.x;
rect.origin.y = self->bounds.origin.y;
break ;
case GSK_CORNER_TOP_RIGHT:
rect.origin.x = self->bounds.origin.x + self->bounds.size.width - self->corner[corner].width;
rect.origin.y = self->bounds.origin.y;
break ;
case GSK_CORNER_BOTTOM_RIGHT:
rect.origin.x = self->bounds.origin.x + self->bounds.size.width - self->corner[corner].width;
rect.origin.y = self->bounds.origin.y + self->bounds.size.height - self->corner[corner].height;
break ;
case GSK_CORNER_BOTTOM_LEFT:
rect.origin.x = self->bounds.origin.x;
rect.origin.y = self->bounds.origin.y + self->bounds.size.height - self->corner[corner].height;
break ;
default :
g_assert_not_reached ();
}
return graphene_rect_contains_point (&rect, point);
}
Messung V0.5 in Prozent C=98 H=97 G=97
¤ Dauer der Verarbeitung: 0.20 Sekunden
(vorverarbeitet am 2026-07-03)
¤
*© Formatika GbR, Deutschland