gsk: Add GskRoundedRect

It's essentially a port of GtkRoundedBox to graphene.
This commit is contained in:
Benjamin Otte 2016-12-13 19:02:12 +01:00
parent 0a0cbcdcb6
commit 59d638a09f
7 changed files with 444 additions and 1 deletions

View File

@ -57,3 +57,15 @@ GskRenderNodeClass
gsk_render_node_get_type
GSK_TYPE_BLEND_MODE
</SECTION>
<SECTION>
<FILE>GskRoundedRect</FILE>
GskCorner
GskRoundedRect
gsk_rounded_rect_init
gsk_rounded_rect_init_copy
gsk_rounded_rect_init_from_rect
gsk_rounded_rect_normalize
gsk_rounded_rect_offset
gsk_rounded_rect_is_rectilinear
</SECTION>

View File

@ -47,6 +47,7 @@ gsk_public_source_h = \
gskenums.h \
gskrenderer.h \
gskrendernode.h \
gskroundedrect.h \
gsktexture.h \
gsktypes.h
gsk_private_source_h = \
@ -60,12 +61,14 @@ gsk_private_source_h = \
gskprofilerprivate.h \
gskrendererprivate.h \
gskrendernodeprivate.h \
gskroundedrectprivate.h \
gskshaderbuilderprivate.h \
gsktextureprivate.h
gsk_public_source_c = \
gskrenderer.c \
gskrendernode.c \
gskrendernodeimpl.c \
gskroundedrect.c \
gsktexture.c
gsk_private_source_c = \
$(gsk_private_vulkan_source_c) \

View File

@ -23,6 +23,7 @@
#include <gsk/gskenums.h>
#include <gsk/gskrenderer.h>
#include <gsk/gskrendernode.h>
#include <gsk/gskroundedrect.h>
#include <gsk/gsktexture.h>
#include <gsk/gsktypes.h>

View File

@ -33,6 +33,7 @@
* matrix transform
* @GSK_OPACITY_NODE: A node that changes the opacity of its child
* @GSK_CLIP_NODE: A node that clips its child to a rectangular area
* @GSK_ROUNDED_CLIP_NODE: A node that clips its child to a rounded rectangle
*
* The type of a node determines what the node is rendering.
*
@ -46,7 +47,8 @@ typedef enum {
GSK_TEXTURE_NODE,
GSK_TRANSFORM_NODE,
GSK_OPACITY_NODE,
GSK_CLIP_NODE
GSK_CLIP_NODE,
GSK_ROUNDED_CLIP_NODE
} GskRenderNodeType;
/**
@ -109,4 +111,20 @@ typedef enum {
GSK_BLEND_MODE_EXCLUSION
} GskBlendMode;
/**
* GskCorner:
* @GSK_CORNER_TOP_LEFT: The top left corner
* @GSK_CORNER_TOP_RIGHT: The top right corner
* @GSK_CORNER_BOTTOM_RIGHT: The bottom right corner
* @GSK_CORNER_BOTTOM_LEFT: The bottom left corner
*
* The corner indices used by #GskRoundedRect.
*/
typedef enum {
GSK_CORNER_TOP_LEFT,
GSK_CORNER_TOP_RIGHT,
GSK_CORNER_BOTTOM_RIGHT,
GSK_CORNER_BOTTOM_LEFT
} GskCorner;
#endif /* __GSK_TYPES_H__ */

298
gsk/gskroundedrect.c Normal file
View File

@ -0,0 +1,298 @@
/* 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/>.
*/
/**
* SECTION:GskRoundedRect
* @Title: GskRoundedRect
* @Short_description: A rounded rectangle
*
* #GskRoundedRect defines a rectangle with rounded corners, as is commonly
* used in drawing.
*
* Operations on a #GskRoundedRect will normalize the rectangle, to
* ensure that the bounds are normalized and that the corner sizes don't exceed
* the size of the rectangle. 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 "gskdebugprivate.h"
#include <math.h>
static void
gsk_rounded_rect_normalize_in_place (GskRoundedRect *self)
{
float factor = 1.0;
float corners;
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 */
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);
for (i = 0; i < 4; i++)
graphene_size_scale (&self->corner[i], factor, &self->corner[i]);
}
/**
* gsk_rounded_rect_init:
* @self: The #GskRoundedRect 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 the given #GskRoundedRect with the given values.
*
* This function will implicitly normalize the #GskRoundedRect
* before returning.
*
* Returns: (transfer none): the initialized rectangle
*
* Since: 3.90
*/
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: a #GskRoundedRect
* @src: a #GskRoundedRect
*
* Initializes @self using the given @src rectangle.
*
* This function will implicitly normalize the #GskRoundedRect
* before returning.
*
* Returns: (transfer none): the initialized rectangle
*
* Since: 3.90
*/
GskRoundedRect *
gsk_rounded_rect_init_copy (GskRoundedRect *self,
const GskRoundedRect *src)
{
*self = *src;
gsk_rounded_rect_normalize_in_place (self);
return self;
}
/**
* gsk_rounded_rect_init_from_rect:
* @self: a #GskRoundedRect
* @bounds: a #graphene_rect_t
* @radius: the border radius
*
* Initializes @self to the given @bounds and sets the radius of all
* four corners to @radius.
*
* Returns: (transfer none): the initialized 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 #GskRoundedRect
*
* Normalizes the passed rectangle.
*
* this function will ensure that the bounds of the rectanlge are normalized
* and ensure that the corner values are positive and the corners do not overlap.
*
* Returns: (transfer none): the normalized rectangle
*
* Since: 3.90
*/
GskRoundedRect *
gsk_rounded_rect_normalize (GskRoundedRect *self)
{
gsk_rounded_rect_normalize_in_place (self);
return self;
}
/**
* gsk_rounded_rect_offset:
* @self: a #GskRoundedRect
* @d_x: the horizontal offset
* @d_y: the vertical offset
*
* Offsets the bound's origin by @dx and @dy.
*
* The size and corners of the rectangle are unchanged.
*
* Returns: (transfer none): the offset rectangle
*
* Since: 3.90
*/
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;
}
/**
* gsk_rounded_rect_is_rectilinear:
* @self: the #GskRoundedRect to check
*
* Checks if all corners of @self are right angles and the
* rectangle covers all of its bounds.
*
* This information can be used to decide if gsk_clip_node_new()
* or gsk_rounded_clip_node_new() should be called.
*
* Returns: %TRUE if the rectangle is rectilinear
**/
gboolean
gsk_rounded_rect_is_rectilinear (GskRoundedRect *self)
{
guint i;
for (i = 0; i < 4; i++)
{
if (self->corner[i].width > 0 ||
self->corner[i].height > 0)
return FALSE;
}
return TRUE;
}
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);
}

96
gsk/gskroundedrect.h Normal file
View File

@ -0,0 +1,96 @@
/* 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/>.
*/
#ifndef __GSK_ROUNDED_RECT_H__
#define __GSK_ROUNDED_RECT_H__
#if !defined (__GSK_H_INSIDE__) && !defined (GSK_COMPILATION)
#error "Only <gsk/gsk.h> can be included directly."
#endif
#include <gsk/gsktypes.h>
G_BEGIN_DECLS
/**
* GSK_ROUNDED_RECT_INIT:
* @_x: the X coordinate of the origin
* @_y: the Y coordinate of the origin
* @_w: the width
* @_h: the height
*
* Initializes a #GskRoundedRect when declaring it.
*
* Since: 3.90
*/
#define GSK_ROUNDED_RECT_INIT(_x,_y,_w,_h) (GskRoundedRect) { .rect = GRAPHENE_RECT_INIT(_x,_y,_w,_h) }
/**
* GskRoundedRect:
* @bounds: the bounds of the rectangle
* @corner: the size of the 4 rounded corners
*
* A rectanglular region with rounded corners.
*
* Application code should normalize rectangles using gsk_rounded_rect_normalize();
* this function will ensure that the bounds of the rectanlge 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.
*
* Since: 3.90
*/
typedef struct _GskRoundedRect GskRoundedRect;
struct _GskRoundedRect
{
graphene_rect_t bounds;
graphene_size_t corner[4];
};
GDK_AVAILABLE_IN_3_90
GskRoundedRect * gsk_rounded_rect_init (GskRoundedRect *self,
const graphene_rect_t *rect,
const graphene_size_t *top_left,
const graphene_size_t *top_right,
const graphene_size_t *bottom_right,
const graphene_size_t *bottom_left);
GDK_AVAILABLE_IN_3_90
GskRoundedRect * gsk_rounded_rect_init_copy (GskRoundedRect *self,
const GskRoundedRect *src);
GDK_AVAILABLE_IN_3_90
GskRoundedRect * gsk_rounded_rect_init_from_rect (GskRoundedRect *self,
const graphene_rect_t *bounds,
float radius);
GDK_AVAILABLE_IN_3_90
GskRoundedRect * gsk_rounded_rect_normalize (GskRoundedRect *self);
GDK_AVAILABLE_IN_3_90
GskRoundedRect * gsk_rounded_rect_offset (GskRoundedRect *self,
float dx,
float dy);
GDK_AVAILABLE_IN_3_90
gboolean gsk_rounded_rect_is_rectilinear (GskRoundedRect *self);
G_END_DECLS
#endif /* __GSK_ROUNDED_RECT_H__ */

View File

@ -0,0 +1,15 @@
#ifndef __GSK_ROUNDED_RECT_PRIVATE_H__
#define __GSK_ROUNDED_RECT_PRIVATE_H__
#include "gskroundedrect.h"
#include <cairo.h>
G_BEGIN_DECLS
void gsk_rounded_rect_path (const GskRoundedRect *self,
cairo_t *cr);
G_END_DECLS
#endif /* __GSK_ROUNDED_RECT_PRIVATE_H__ */