[gtk+/wip/otte/rendernode: 22/34] gsk: Add GskRoundedRect



commit 467e222b517f155d2b289b9886ca991d597a1bae
Author: Benjamin Otte <otte redhat com>
Date:   Tue Dec 13 19:02:12 2016 +0100

    gsk: Add GskRoundedRect
    
    It's essentially a port of GtkRoundedBox to graphene.

 docs/reference/gsk/gsk4-sections.txt |   12 ++
 gsk/Makefile.am                      |    3 +
 gsk/gsk.h                            |    1 +
 gsk/gskenums.h                       |   20 +++-
 gsk/gskroundedrect.c                 |  298 ++++++++++++++++++++++++++++++++++
 gsk/gskroundedrect.h                 |   96 +++++++++++
 gsk/gskroundedrectprivate.h          |   15 ++
 7 files changed, 444 insertions(+), 1 deletions(-)
---
diff --git a/docs/reference/gsk/gsk4-sections.txt b/docs/reference/gsk/gsk4-sections.txt
index 57b8657..5ea9fbd 100644
--- a/docs/reference/gsk/gsk4-sections.txt
+++ b/docs/reference/gsk/gsk4-sections.txt
@@ -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>
diff --git a/gsk/Makefile.am b/gsk/Makefile.am
index d7c433c..f27cf2f 100644
--- a/gsk/Makefile.am
+++ b/gsk/Makefile.am
@@ -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) \
diff --git a/gsk/gsk.h b/gsk/gsk.h
index 92ce028..e330600 100644
--- a/gsk/gsk.h
+++ b/gsk/gsk.h
@@ -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>
diff --git a/gsk/gskenums.h b/gsk/gskenums.h
index 7b37715..670a034 100644
--- a/gsk/gskenums.h
+++ b/gsk/gskenums.h
@@ -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__ */
diff --git a/gsk/gskroundedrect.c b/gsk/gskroundedrect.c
new file mode 100644
index 0000000..0e4173a
--- /dev/null
+++ b/gsk/gskroundedrect.c
@@ -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);
+}
+
diff --git a/gsk/gskroundedrect.h b/gsk/gskroundedrect.h
new file mode 100644
index 0000000..96551ed
--- /dev/null
+++ b/gsk/gskroundedrect.h
@@ -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__ */
diff --git a/gsk/gskroundedrectprivate.h b/gsk/gskroundedrectprivate.h
new file mode 100644
index 0000000..39b968c
--- /dev/null
+++ b/gsk/gskroundedrectprivate.h
@@ -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__ */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]