mirror of
https://gitlab.gnome.org/GNOME/gtk.git
synced 2024-11-10 02:40:11 +00:00
6a02fa4be8
Add a constructor that takes a color state, a getter for it, and also a getter for the color coordinates that avoids GdkRGBA.
7885 lines
233 KiB
C
7885 lines
233 KiB
C
/* 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/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "gskrendernodeprivate.h"
|
|
|
|
#include "gskcairoblurprivate.h"
|
|
#include "gskcairorenderer.h"
|
|
#include "gskdebugprivate.h"
|
|
#include "gskdiffprivate.h"
|
|
#include "gl/gskglrenderer.h"
|
|
#include "gskpathprivate.h"
|
|
#include "gskrectprivate.h"
|
|
#include "gskrendererprivate.h"
|
|
#include "gskroundedrectprivate.h"
|
|
#include "gskstrokeprivate.h"
|
|
#include "gsktransformprivate.h"
|
|
#include "gskprivate.h"
|
|
|
|
#include "gdk/gdkcairoprivate.h"
|
|
#include "gdk/gdkcolorstateprivate.h"
|
|
#include "gdk/gdkmemoryformatprivate.h"
|
|
#include "gdk/gdkprivate.h"
|
|
#include "gdk/gdkrectangleprivate.h"
|
|
#include "gdk/gdksubsurfaceprivate.h"
|
|
#include "gdk/gdktextureprivate.h"
|
|
#include "gdk/gdktexturedownloaderprivate.h"
|
|
#include "gdk/gdkrgbaprivate.h"
|
|
#include "gdk/gdkcolorstateprivate.h"
|
|
|
|
#include <cairo.h>
|
|
#ifdef CAIRO_HAS_SVG_SURFACE
|
|
#include <cairo-svg.h>
|
|
#endif
|
|
#include <hb-ot.h>
|
|
|
|
/* for oversized image fallback - we use a smaller size than Cairo actually
|
|
* allows to avoid rounding errors in Cairo */
|
|
#define MAX_CAIRO_IMAGE_WIDTH 16384
|
|
#define MAX_CAIRO_IMAGE_HEIGHT 16384
|
|
|
|
/* maximal number of rectangles we keep in a diff region before we throw
|
|
* the towel and just use the bounding box of the parent node.
|
|
* Meant to avoid performance corner cases.
|
|
*/
|
|
#define MAX_RECTS_IN_DIFF 30
|
|
|
|
static gboolean
|
|
gsk_color_stops_are_opaque (const GskColorStop *stops,
|
|
gsize n_stops)
|
|
{
|
|
gsize i;
|
|
|
|
for (i = 0; i < n_stops; i++)
|
|
{
|
|
if (!gdk_rgba_is_opaque (&stops[i].color))
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* FIXME: Replace this once GdkColor lands */
|
|
static inline GdkMemoryDepth
|
|
my_color_get_depth (const GdkRGBA *rgba)
|
|
{
|
|
return gdk_color_state_get_depth (GDK_COLOR_STATE_SRGB);
|
|
}
|
|
|
|
/* FIXME: Replace this once GdkColor lands */
|
|
static inline GdkMemoryDepth
|
|
my_color_stops_get_depth (const GskColorStop *stops,
|
|
gsize n_stops)
|
|
{
|
|
return gdk_color_state_get_depth (GDK_COLOR_STATE_SRGB);
|
|
}
|
|
|
|
/* apply a rectangle that bounds @rect in
|
|
* pixel-aligned device coordinates.
|
|
*
|
|
* This is useful for clipping to minimize the rectangle
|
|
* in push_group() or when blurring.
|
|
*/
|
|
static void
|
|
gsk_cairo_rectangle_pixel_aligned (cairo_t *cr,
|
|
const graphene_rect_t *rect)
|
|
{
|
|
double x0, x1, x2, x3;
|
|
double y0, y1, y2, y3;
|
|
double xmin, xmax, ymin, ymax;
|
|
|
|
x0 = rect->origin.x;
|
|
y0 = rect->origin.y;
|
|
cairo_user_to_device (cr, &x0, &y0);
|
|
x1 = rect->origin.x + rect->size.width;
|
|
y1 = rect->origin.y;
|
|
cairo_user_to_device (cr, &x1, &y1);
|
|
x2 = rect->origin.x;
|
|
y2 = rect->origin.y + rect->size.height;
|
|
cairo_user_to_device (cr, &x2, &y2);
|
|
x3 = rect->origin.x + rect->size.width;
|
|
y3 = rect->origin.y + rect->size.height;
|
|
cairo_user_to_device (cr, &x3, &y3);
|
|
|
|
xmin = MIN (MIN (x0, x1), MIN (x2, x3));
|
|
ymin = MIN (MIN (y0, y1), MIN (y2, y3));
|
|
xmax = MAX (MAX (x0, x1), MAX (x2, x3));
|
|
ymax = MAX (MAX (y0, y1), MAX (y2, y3));
|
|
|
|
xmin = floor (xmin);
|
|
ymin = floor (ymin);
|
|
xmax = ceil (xmax);
|
|
ymax = ceil (ymax);
|
|
|
|
cairo_save (cr);
|
|
cairo_identity_matrix (cr);
|
|
cairo_rectangle (cr, xmin, ymin, xmax - xmin, ymax - ymin);
|
|
cairo_restore (cr);
|
|
}
|
|
|
|
static void
|
|
_graphene_rect_init_from_clip_extents (graphene_rect_t *rect,
|
|
cairo_t *cr)
|
|
{
|
|
double x1c, y1c, x2c, y2c;
|
|
|
|
cairo_clip_extents (cr, &x1c, &y1c, &x2c, &y2c);
|
|
gsk_rect_init (rect, x1c, y1c, x2c - x1c, y2c - y1c);
|
|
}
|
|
|
|
static void
|
|
region_union_region_affine (cairo_region_t *region,
|
|
const cairo_region_t *sub,
|
|
float scale_x,
|
|
float scale_y,
|
|
float offset_x,
|
|
float offset_y)
|
|
{
|
|
cairo_rectangle_int_t rect;
|
|
int i;
|
|
|
|
for (i = 0; i < cairo_region_num_rectangles (sub); i++)
|
|
{
|
|
cairo_region_get_rectangle (sub, i, &rect);
|
|
gdk_rectangle_transform_affine (&rect, scale_x, scale_y, offset_x, offset_y, &rect);
|
|
cairo_region_union_rectangle (region, &rect);
|
|
}
|
|
}
|
|
|
|
/* {{{ GSK_COLOR_NODE */
|
|
|
|
/**
|
|
* GskColorNode:
|
|
*
|
|
* A render node for a solid color.
|
|
*/
|
|
struct _GskColorNode
|
|
{
|
|
GskRenderNode render_node;
|
|
GdkColor color;
|
|
};
|
|
|
|
static void
|
|
gsk_color_node_finalize (GskRenderNode *node)
|
|
{
|
|
GskColorNode *self = (GskColorNode *) node;
|
|
GskRenderNodeClass *parent_class = g_type_class_peek (g_type_parent (GSK_TYPE_COLOR_NODE));
|
|
|
|
gdk_color_finish (&self->color);
|
|
|
|
parent_class->finalize (node);
|
|
}
|
|
|
|
static void
|
|
gsk_color_node_draw (GskRenderNode *node,
|
|
cairo_t *cr,
|
|
GdkColorState *ccs)
|
|
{
|
|
GskColorNode *self = (GskColorNode *) node;
|
|
|
|
gdk_cairo_set_source_color (cr, ccs, &self->color);
|
|
gdk_cairo_rect (cr, &node->bounds);
|
|
cairo_fill (cr);
|
|
}
|
|
|
|
static void
|
|
gsk_color_node_diff (GskRenderNode *node1,
|
|
GskRenderNode *node2,
|
|
GskDiffData *data)
|
|
{
|
|
GskColorNode *self1 = (GskColorNode *) node1;
|
|
GskColorNode *self2 = (GskColorNode *) node2;
|
|
|
|
if (gsk_rect_equal (&node1->bounds, &node2->bounds) &&
|
|
gdk_color_equal (&self1->color, &self2->color))
|
|
return;
|
|
|
|
gsk_render_node_diff_impossible (node1, node2, data);
|
|
}
|
|
|
|
static void
|
|
gsk_color_node_class_init (gpointer g_class,
|
|
gpointer class_data)
|
|
{
|
|
GskRenderNodeClass *node_class = g_class;
|
|
|
|
node_class->node_type = GSK_COLOR_NODE;
|
|
|
|
node_class->finalize = gsk_color_node_finalize;
|
|
node_class->draw = gsk_color_node_draw;
|
|
node_class->diff = gsk_color_node_diff;
|
|
}
|
|
|
|
/**
|
|
* gsk_color_node_get_color:
|
|
* @node: (type GskColorNode): a `GskRenderNode`
|
|
*
|
|
* Retrieves the color of the given @node.
|
|
*
|
|
* The value returned by this function will not be correct
|
|
* if the render node was created for a non-sRGB color.
|
|
*
|
|
* Returns: (transfer none): the color of the node
|
|
*/
|
|
const GdkRGBA *
|
|
gsk_color_node_get_color (const GskRenderNode *node)
|
|
{
|
|
GskColorNode *self = (GskColorNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_COLOR_NODE), NULL);
|
|
|
|
/* NOTE: This is only correct for nodes with sRGB colors */
|
|
return (const GdkRGBA *) &self->color.values;
|
|
}
|
|
|
|
/*< private >
|
|
* gsk_color_node_get_color2:
|
|
* @node: (type GskColorNode): a `GskRenderNode`
|
|
*
|
|
* Retrieves the color of the given @node.
|
|
*
|
|
* Returns: (transfer none): the color of the node
|
|
*/
|
|
const GdkColor *
|
|
gsk_color_node_get_color2 (const GskRenderNode *node)
|
|
{
|
|
GskColorNode *self = (GskColorNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_COLOR_NODE), NULL);
|
|
|
|
return &self->color;
|
|
}
|
|
|
|
/**
|
|
* gsk_color_node_new:
|
|
* @rgba: a `GdkRGBA` specifying a color
|
|
* @bounds: the rectangle to render the color into
|
|
*
|
|
* Creates a `GskRenderNode` that will render the color specified by @rgba into
|
|
* the area given by @bounds.
|
|
*
|
|
* Returns: (transfer full) (type GskColorNode): A new `GskRenderNode`
|
|
*/
|
|
GskRenderNode *
|
|
gsk_color_node_new (const GdkRGBA *rgba,
|
|
const graphene_rect_t *bounds)
|
|
{
|
|
GdkColor color;
|
|
GskRenderNode *node;
|
|
|
|
gdk_color_init_from_rgba (&color, rgba);
|
|
node = gsk_color_node_new2 (&color, bounds);
|
|
gdk_color_finish (&color);
|
|
|
|
return node;
|
|
}
|
|
|
|
/*< private >
|
|
* gsk_color_node_new2:
|
|
* @color: a `GdkColor` specifying a color
|
|
* @bounds: the rectangle to render the color into
|
|
*
|
|
* Creates a `GskRenderNode` that will render the color specified by @color
|
|
* into the area given by @bounds.
|
|
*
|
|
* Returns: (transfer full) (type GskColorNode): A new `GskRenderNode`
|
|
*/
|
|
GskRenderNode *
|
|
gsk_color_node_new2 (const GdkColor *color,
|
|
const graphene_rect_t *bounds)
|
|
{
|
|
GskColorNode *self;
|
|
GskRenderNode *node;
|
|
|
|
g_return_val_if_fail (color != NULL, NULL);
|
|
g_return_val_if_fail (bounds != NULL, NULL);
|
|
|
|
self = gsk_render_node_alloc (GSK_COLOR_NODE);
|
|
node = (GskRenderNode *) self;
|
|
node->offscreen_for_opacity = FALSE;
|
|
node->fully_opaque = gdk_color_is_opaque (color);
|
|
node->preferred_depth = gdk_color_get_depth (color);
|
|
|
|
gdk_color_init_copy (&self->color, color);
|
|
|
|
gsk_rect_init_from_rect (&node->bounds, bounds);
|
|
gsk_rect_normalize (&node->bounds);
|
|
|
|
return node;
|
|
}
|
|
|
|
/* }}} */
|
|
/* {{{ GSK_LINEAR_GRADIENT_NODE */
|
|
|
|
/**
|
|
* GskRepeatingLinearGradientNode:
|
|
*
|
|
* A render node for a repeating linear gradient.
|
|
*/
|
|
|
|
/**
|
|
* GskLinearGradientNode:
|
|
*
|
|
* A render node for a linear gradient.
|
|
*/
|
|
struct _GskLinearGradientNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
graphene_point_t start;
|
|
graphene_point_t end;
|
|
|
|
gsize n_stops;
|
|
GskColorStop *stops;
|
|
};
|
|
|
|
static void
|
|
gsk_linear_gradient_node_finalize (GskRenderNode *node)
|
|
{
|
|
GskLinearGradientNode *self = (GskLinearGradientNode *) node;
|
|
GskRenderNodeClass *parent_class = g_type_class_peek (g_type_parent (GSK_TYPE_LINEAR_GRADIENT_NODE));
|
|
|
|
g_free (self->stops);
|
|
|
|
parent_class->finalize (node);
|
|
}
|
|
|
|
static void
|
|
gsk_linear_gradient_node_draw (GskRenderNode *node,
|
|
cairo_t *cr,
|
|
GdkColorState *ccs)
|
|
{
|
|
GskLinearGradientNode *self = (GskLinearGradientNode *) node;
|
|
cairo_pattern_t *pattern;
|
|
gsize i;
|
|
|
|
pattern = cairo_pattern_create_linear (self->start.x, self->start.y,
|
|
self->end.x, self->end.y);
|
|
|
|
if (gsk_render_node_get_node_type (node) == GSK_REPEATING_LINEAR_GRADIENT_NODE)
|
|
cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
|
|
|
|
if (self->stops[0].offset > 0.0)
|
|
gdk_cairo_pattern_add_color_stop_rgba_ccs (pattern, ccs, 0.0, &self->stops[0].color);
|
|
for (i = 0; i < self->n_stops; i++)
|
|
{
|
|
gdk_cairo_pattern_add_color_stop_rgba_ccs (pattern,
|
|
ccs,
|
|
self->stops[i].offset,
|
|
&self->stops[i].color);
|
|
}
|
|
if (self->stops[self->n_stops-1].offset < 1.0)
|
|
gdk_cairo_pattern_add_color_stop_rgba_ccs (pattern, ccs, 1.0, &self->stops[self->n_stops-1].color);
|
|
|
|
cairo_set_source (cr, pattern);
|
|
cairo_pattern_destroy (pattern);
|
|
|
|
gdk_cairo_rect (cr, &node->bounds);
|
|
cairo_fill (cr);
|
|
}
|
|
|
|
static void
|
|
gsk_linear_gradient_node_diff (GskRenderNode *node1,
|
|
GskRenderNode *node2,
|
|
GskDiffData *data)
|
|
{
|
|
GskLinearGradientNode *self1 = (GskLinearGradientNode *) node1;
|
|
GskLinearGradientNode *self2 = (GskLinearGradientNode *) node2;
|
|
|
|
if (graphene_point_equal (&self1->start, &self2->start) &&
|
|
graphene_point_equal (&self1->end, &self2->end) &&
|
|
self1->n_stops == self2->n_stops)
|
|
{
|
|
gsize i;
|
|
|
|
for (i = 0; i < self1->n_stops; i++)
|
|
{
|
|
GskColorStop *stop1 = &self1->stops[i];
|
|
GskColorStop *stop2 = &self2->stops[i];
|
|
|
|
if (stop1->offset == stop2->offset &&
|
|
gdk_rgba_equal (&stop1->color, &stop2->color))
|
|
continue;
|
|
|
|
gsk_render_node_diff_impossible (node1, node2, data);
|
|
return;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
gsk_render_node_diff_impossible (node1, node2, data);
|
|
}
|
|
|
|
static void
|
|
gsk_linear_gradient_node_class_init (gpointer g_class,
|
|
gpointer class_data)
|
|
{
|
|
GskRenderNodeClass *node_class = g_class;
|
|
|
|
node_class->node_type = GSK_LINEAR_GRADIENT_NODE;
|
|
|
|
node_class->finalize = gsk_linear_gradient_node_finalize;
|
|
node_class->draw = gsk_linear_gradient_node_draw;
|
|
node_class->diff = gsk_linear_gradient_node_diff;
|
|
}
|
|
|
|
static void
|
|
gsk_repeating_linear_gradient_node_class_init (gpointer g_class,
|
|
gpointer class_data)
|
|
{
|
|
GskRenderNodeClass *node_class = g_class;
|
|
|
|
node_class->node_type = GSK_REPEATING_LINEAR_GRADIENT_NODE;
|
|
|
|
node_class->finalize = gsk_linear_gradient_node_finalize;
|
|
node_class->draw = gsk_linear_gradient_node_draw;
|
|
node_class->diff = gsk_linear_gradient_node_diff;
|
|
}
|
|
|
|
/**
|
|
* gsk_linear_gradient_node_new:
|
|
* @bounds: the rectangle to render the linear gradient into
|
|
* @start: the point at which the linear gradient will begin
|
|
* @end: the point at which the linear gradient will finish
|
|
* @color_stops: (array length=n_color_stops): a pointer to an array of
|
|
* `GskColorStop` defining the gradient. The offsets of all color stops
|
|
* must be increasing. The first stop's offset must be >= 0 and the last
|
|
* stop's offset must be <= 1.
|
|
* @n_color_stops: the number of elements in @color_stops
|
|
*
|
|
* Creates a `GskRenderNode` that will create a linear gradient from the given
|
|
* points and color stops, and render that into the area given by @bounds.
|
|
*
|
|
* Returns: (transfer full) (type GskLinearGradientNode): A new `GskRenderNode`
|
|
*/
|
|
GskRenderNode *
|
|
gsk_linear_gradient_node_new (const graphene_rect_t *bounds,
|
|
const graphene_point_t *start,
|
|
const graphene_point_t *end,
|
|
const GskColorStop *color_stops,
|
|
gsize n_color_stops)
|
|
{
|
|
GskLinearGradientNode *self;
|
|
GskRenderNode *node;
|
|
gsize i;
|
|
|
|
g_return_val_if_fail (bounds != NULL, NULL);
|
|
g_return_val_if_fail (start != NULL, NULL);
|
|
g_return_val_if_fail (end != NULL, NULL);
|
|
g_return_val_if_fail (color_stops != NULL, NULL);
|
|
g_return_val_if_fail (n_color_stops >= 2, NULL);
|
|
g_return_val_if_fail (color_stops[0].offset >= 0, NULL);
|
|
for (i = 1; i < n_color_stops; i++)
|
|
g_return_val_if_fail (color_stops[i].offset >= color_stops[i - 1].offset, NULL);
|
|
g_return_val_if_fail (color_stops[n_color_stops - 1].offset <= 1, NULL);
|
|
|
|
self = gsk_render_node_alloc (GSK_LINEAR_GRADIENT_NODE);
|
|
node = (GskRenderNode *) self;
|
|
node->offscreen_for_opacity = FALSE;
|
|
node->fully_opaque = gsk_color_stops_are_opaque (color_stops, n_color_stops);
|
|
node->preferred_depth = my_color_stops_get_depth (color_stops, n_color_stops);
|
|
|
|
gsk_rect_init_from_rect (&node->bounds, bounds);
|
|
gsk_rect_normalize (&node->bounds);
|
|
graphene_point_init_from_point (&self->start, start);
|
|
graphene_point_init_from_point (&self->end, end);
|
|
|
|
self->n_stops = n_color_stops;
|
|
self->stops = g_malloc_n (n_color_stops, sizeof (GskColorStop));
|
|
memcpy (self->stops, color_stops, n_color_stops * sizeof (GskColorStop));
|
|
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* gsk_repeating_linear_gradient_node_new:
|
|
* @bounds: the rectangle to render the linear gradient into
|
|
* @start: the point at which the linear gradient will begin
|
|
* @end: the point at which the linear gradient will finish
|
|
* @color_stops: (array length=n_color_stops): a pointer to an array of
|
|
* `GskColorStop` defining the gradient. The offsets of all color stops
|
|
* must be increasing. The first stop's offset must be >= 0 and the last
|
|
* stop's offset must be <= 1.
|
|
* @n_color_stops: the number of elements in @color_stops
|
|
*
|
|
* Creates a `GskRenderNode` that will create a repeating linear gradient
|
|
* from the given points and color stops, and render that into the area
|
|
* given by @bounds.
|
|
*
|
|
* Returns: (transfer full) (type GskRepeatingLinearGradientNode): A new `GskRenderNode`
|
|
*/
|
|
GskRenderNode *
|
|
gsk_repeating_linear_gradient_node_new (const graphene_rect_t *bounds,
|
|
const graphene_point_t *start,
|
|
const graphene_point_t *end,
|
|
const GskColorStop *color_stops,
|
|
gsize n_color_stops)
|
|
{
|
|
GskLinearGradientNode *self;
|
|
GskRenderNode *node;
|
|
gsize i;
|
|
|
|
g_return_val_if_fail (bounds != NULL, NULL);
|
|
g_return_val_if_fail (start != NULL, NULL);
|
|
g_return_val_if_fail (end != NULL, NULL);
|
|
g_return_val_if_fail (color_stops != NULL, NULL);
|
|
g_return_val_if_fail (n_color_stops >= 2, NULL);
|
|
g_return_val_if_fail (color_stops[0].offset >= 0, NULL);
|
|
for (i = 1; i < n_color_stops; i++)
|
|
g_return_val_if_fail (color_stops[i].offset >= color_stops[i - 1].offset, NULL);
|
|
g_return_val_if_fail (color_stops[n_color_stops - 1].offset <= 1, NULL);
|
|
|
|
self = gsk_render_node_alloc (GSK_REPEATING_LINEAR_GRADIENT_NODE);
|
|
node = (GskRenderNode *) self;
|
|
node->offscreen_for_opacity = FALSE;
|
|
node->fully_opaque = gsk_color_stops_are_opaque (color_stops, n_color_stops);
|
|
node->preferred_depth = my_color_stops_get_depth (color_stops, n_color_stops);
|
|
|
|
gsk_rect_init_from_rect (&node->bounds, bounds);
|
|
gsk_rect_normalize (&node->bounds);
|
|
graphene_point_init_from_point (&self->start, start);
|
|
graphene_point_init_from_point (&self->end, end);
|
|
|
|
self->stops = g_malloc_n (n_color_stops, sizeof (GskColorStop));
|
|
memcpy (self->stops, color_stops, n_color_stops * sizeof (GskColorStop));
|
|
self->n_stops = n_color_stops;
|
|
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* gsk_linear_gradient_node_get_start:
|
|
* @node: (type GskLinearGradientNode): a `GskRenderNode` for a linear gradient
|
|
*
|
|
* Retrieves the initial point of the linear gradient.
|
|
*
|
|
* Returns: (transfer none): the initial point
|
|
*/
|
|
const graphene_point_t *
|
|
gsk_linear_gradient_node_get_start (const GskRenderNode *node)
|
|
{
|
|
const GskLinearGradientNode *self = (const GskLinearGradientNode *) node;
|
|
|
|
return &self->start;
|
|
}
|
|
|
|
/**
|
|
* gsk_linear_gradient_node_get_end:
|
|
* @node: (type GskLinearGradientNode): a `GskRenderNode` for a linear gradient
|
|
*
|
|
* Retrieves the final point of the linear gradient.
|
|
*
|
|
* Returns: (transfer none): the final point
|
|
*/
|
|
const graphene_point_t *
|
|
gsk_linear_gradient_node_get_end (const GskRenderNode *node)
|
|
{
|
|
const GskLinearGradientNode *self = (const GskLinearGradientNode *) node;
|
|
|
|
return &self->end;
|
|
}
|
|
|
|
/**
|
|
* gsk_linear_gradient_node_get_n_color_stops:
|
|
* @node: (type GskLinearGradientNode): a `GskRenderNode` for a linear gradient
|
|
*
|
|
* Retrieves the number of color stops in the gradient.
|
|
*
|
|
* Returns: the number of color stops
|
|
*/
|
|
gsize
|
|
gsk_linear_gradient_node_get_n_color_stops (const GskRenderNode *node)
|
|
{
|
|
const GskLinearGradientNode *self = (const GskLinearGradientNode *) node;
|
|
|
|
return self->n_stops;
|
|
}
|
|
|
|
/**
|
|
* gsk_linear_gradient_node_get_color_stops:
|
|
* @node: (type GskLinearGradientNode): a `GskRenderNode` for a linear gradient
|
|
* @n_stops: (out) (optional): the number of color stops in the returned array
|
|
*
|
|
* Retrieves the color stops in the gradient.
|
|
*
|
|
* Returns: (array length=n_stops): the color stops in the gradient
|
|
*/
|
|
const GskColorStop *
|
|
gsk_linear_gradient_node_get_color_stops (const GskRenderNode *node,
|
|
gsize *n_stops)
|
|
{
|
|
const GskLinearGradientNode *self = (const GskLinearGradientNode *) node;
|
|
|
|
if (n_stops != NULL)
|
|
*n_stops = self->n_stops;
|
|
|
|
return self->stops;
|
|
}
|
|
|
|
/* }}} */
|
|
/* {{{ GSK_RADIAL_GRADIENT_NODE */
|
|
|
|
/**
|
|
* GskRepeatingRadialGradientNode:
|
|
*
|
|
* A render node for a repeating radial gradient.
|
|
*/
|
|
|
|
/**
|
|
* GskRadialGradientNode:
|
|
*
|
|
* A render node for a radial gradient.
|
|
*/
|
|
struct _GskRadialGradientNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
graphene_point_t center;
|
|
|
|
float hradius;
|
|
float vradius;
|
|
float start;
|
|
float end;
|
|
|
|
gsize n_stops;
|
|
GskColorStop *stops;
|
|
};
|
|
|
|
static void
|
|
gsk_radial_gradient_node_finalize (GskRenderNode *node)
|
|
{
|
|
GskRadialGradientNode *self = (GskRadialGradientNode *) node;
|
|
GskRenderNodeClass *parent_class = g_type_class_peek (g_type_parent (GSK_TYPE_RADIAL_GRADIENT_NODE));
|
|
|
|
g_free (self->stops);
|
|
|
|
parent_class->finalize (node);
|
|
}
|
|
|
|
static void
|
|
gsk_radial_gradient_node_draw (GskRenderNode *node,
|
|
cairo_t *cr,
|
|
GdkColorState *ccs)
|
|
{
|
|
GskRadialGradientNode *self = (GskRadialGradientNode *) node;
|
|
cairo_pattern_t *pattern;
|
|
gsize i;
|
|
|
|
pattern = cairo_pattern_create_radial (0, 0, self->hradius * self->start,
|
|
0, 0, self->hradius * self->end);
|
|
|
|
if (self->hradius != self->vradius)
|
|
{
|
|
cairo_matrix_t matrix;
|
|
|
|
cairo_matrix_init_scale (&matrix, 1.0, self->hradius / self->vradius);
|
|
cairo_pattern_set_matrix (pattern, &matrix);
|
|
}
|
|
|
|
if (gsk_render_node_get_node_type (node) == GSK_REPEATING_RADIAL_GRADIENT_NODE)
|
|
cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
|
|
else
|
|
cairo_pattern_set_extend (pattern, CAIRO_EXTEND_PAD);
|
|
|
|
if (self->stops[0].offset > 0.0)
|
|
gdk_cairo_pattern_add_color_stop_rgba_ccs (pattern, ccs, 0.0, &self->stops[0].color);
|
|
for (i = 0; i < self->n_stops; i++)
|
|
{
|
|
gdk_cairo_pattern_add_color_stop_rgba_ccs (pattern,
|
|
ccs,
|
|
self->stops[i].offset,
|
|
&self->stops[i].color);
|
|
}
|
|
if (self->stops[self->n_stops-1].offset < 1.0)
|
|
gdk_cairo_pattern_add_color_stop_rgba_ccs (pattern, ccs, 1.0, &self->stops[self->n_stops-1].color);
|
|
|
|
gdk_cairo_rect (cr, &node->bounds);
|
|
cairo_translate (cr, self->center.x, self->center.y);
|
|
cairo_set_source (cr, pattern);
|
|
cairo_fill (cr);
|
|
|
|
cairo_pattern_destroy (pattern);
|
|
}
|
|
|
|
static void
|
|
gsk_radial_gradient_node_diff (GskRenderNode *node1,
|
|
GskRenderNode *node2,
|
|
GskDiffData *data)
|
|
{
|
|
GskRadialGradientNode *self1 = (GskRadialGradientNode *) node1;
|
|
GskRadialGradientNode *self2 = (GskRadialGradientNode *) node2;
|
|
|
|
if (graphene_point_equal (&self1->center, &self2->center) &&
|
|
self1->hradius == self2->hradius &&
|
|
self1->vradius == self2->vradius &&
|
|
self1->start == self2->start &&
|
|
self1->end == self2->end &&
|
|
self1->n_stops == self2->n_stops)
|
|
{
|
|
gsize i;
|
|
|
|
for (i = 0; i < self1->n_stops; i++)
|
|
{
|
|
GskColorStop *stop1 = &self1->stops[i];
|
|
GskColorStop *stop2 = &self2->stops[i];
|
|
|
|
if (stop1->offset == stop2->offset &&
|
|
gdk_rgba_equal (&stop1->color, &stop2->color))
|
|
continue;
|
|
|
|
gsk_render_node_diff_impossible (node1, node2, data);
|
|
return;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
gsk_render_node_diff_impossible (node1, node2, data);
|
|
}
|
|
|
|
static void
|
|
gsk_radial_gradient_node_class_init (gpointer g_class,
|
|
gpointer class_data)
|
|
{
|
|
GskRenderNodeClass *node_class = g_class;
|
|
|
|
node_class->node_type = GSK_RADIAL_GRADIENT_NODE;
|
|
|
|
node_class->finalize = gsk_radial_gradient_node_finalize;
|
|
node_class->draw = gsk_radial_gradient_node_draw;
|
|
node_class->diff = gsk_radial_gradient_node_diff;
|
|
}
|
|
|
|
static void
|
|
gsk_repeating_radial_gradient_node_class_init (gpointer g_class,
|
|
gpointer class_data)
|
|
{
|
|
GskRenderNodeClass *node_class = g_class;
|
|
|
|
node_class->node_type = GSK_REPEATING_RADIAL_GRADIENT_NODE;
|
|
|
|
node_class->finalize = gsk_radial_gradient_node_finalize;
|
|
node_class->draw = gsk_radial_gradient_node_draw;
|
|
node_class->diff = gsk_radial_gradient_node_diff;
|
|
}
|
|
|
|
/**
|
|
* gsk_radial_gradient_node_new:
|
|
* @bounds: the bounds of the node
|
|
* @center: the center of the gradient
|
|
* @hradius: the horizontal radius
|
|
* @vradius: the vertical radius
|
|
* @start: a percentage >= 0 that defines the start of the gradient around @center
|
|
* @end: a percentage >= 0 that defines the end of the gradient around @center
|
|
* @color_stops: (array length=n_color_stops): a pointer to an array of
|
|
* `GskColorStop` defining the gradient. The offsets of all color stops
|
|
* must be increasing. The first stop's offset must be >= 0 and the last
|
|
* stop's offset must be <= 1.
|
|
* @n_color_stops: the number of elements in @color_stops
|
|
*
|
|
* Creates a `GskRenderNode` that draws a radial gradient.
|
|
*
|
|
* The radial gradient
|
|
* starts around @center. The size of the gradient is dictated by @hradius
|
|
* in horizontal orientation and by @vradius in vertical orientation.
|
|
*
|
|
* Returns: (transfer full) (type GskRadialGradientNode): A new `GskRenderNode`
|
|
*/
|
|
GskRenderNode *
|
|
gsk_radial_gradient_node_new (const graphene_rect_t *bounds,
|
|
const graphene_point_t *center,
|
|
float hradius,
|
|
float vradius,
|
|
float start,
|
|
float end,
|
|
const GskColorStop *color_stops,
|
|
gsize n_color_stops)
|
|
{
|
|
GskRadialGradientNode *self;
|
|
GskRenderNode *node;
|
|
gsize i;
|
|
|
|
g_return_val_if_fail (bounds != NULL, NULL);
|
|
g_return_val_if_fail (center != NULL, NULL);
|
|
g_return_val_if_fail (hradius > 0., NULL);
|
|
g_return_val_if_fail (vradius > 0., NULL);
|
|
g_return_val_if_fail (start >= 0., NULL);
|
|
g_return_val_if_fail (end >= 0., NULL);
|
|
g_return_val_if_fail (end > start, NULL);
|
|
g_return_val_if_fail (color_stops != NULL, NULL);
|
|
g_return_val_if_fail (n_color_stops >= 2, NULL);
|
|
g_return_val_if_fail (color_stops[0].offset >= 0, NULL);
|
|
for (i = 1; i < n_color_stops; i++)
|
|
g_return_val_if_fail (color_stops[i].offset >= color_stops[i - 1].offset, NULL);
|
|
g_return_val_if_fail (color_stops[n_color_stops - 1].offset <= 1, NULL);
|
|
|
|
self = gsk_render_node_alloc (GSK_RADIAL_GRADIENT_NODE);
|
|
node = (GskRenderNode *) self;
|
|
node->offscreen_for_opacity = FALSE;
|
|
node->fully_opaque = gsk_color_stops_are_opaque (color_stops, n_color_stops);
|
|
node->preferred_depth = my_color_stops_get_depth (color_stops, n_color_stops);
|
|
|
|
gsk_rect_init_from_rect (&node->bounds, bounds);
|
|
gsk_rect_normalize (&node->bounds);
|
|
graphene_point_init_from_point (&self->center, center);
|
|
|
|
self->hradius = hradius;
|
|
self->vradius = vradius;
|
|
self->start = start;
|
|
self->end = end;
|
|
|
|
self->n_stops = n_color_stops;
|
|
self->stops = g_malloc_n (n_color_stops, sizeof (GskColorStop));
|
|
memcpy (self->stops, color_stops, n_color_stops * sizeof (GskColorStop));
|
|
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* gsk_repeating_radial_gradient_node_new:
|
|
* @bounds: the bounds of the node
|
|
* @center: the center of the gradient
|
|
* @hradius: the horizontal radius
|
|
* @vradius: the vertical radius
|
|
* @start: a percentage >= 0 that defines the start of the gradient around @center
|
|
* @end: a percentage >= 0 that defines the end of the gradient around @center
|
|
* @color_stops: (array length=n_color_stops): a pointer to an array of
|
|
* `GskColorStop` defining the gradient. The offsets of all color stops
|
|
* must be increasing. The first stop's offset must be >= 0 and the last
|
|
* stop's offset must be <= 1.
|
|
* @n_color_stops: the number of elements in @color_stops
|
|
*
|
|
* Creates a `GskRenderNode` that draws a repeating radial gradient.
|
|
*
|
|
* The radial gradient starts around @center. The size of the gradient
|
|
* is dictated by @hradius in horizontal orientation and by @vradius
|
|
* in vertical orientation.
|
|
*
|
|
* Returns: (transfer full) (type GskRepeatingRadialGradientNode): A new `GskRenderNode`
|
|
*/
|
|
GskRenderNode *
|
|
gsk_repeating_radial_gradient_node_new (const graphene_rect_t *bounds,
|
|
const graphene_point_t *center,
|
|
float hradius,
|
|
float vradius,
|
|
float start,
|
|
float end,
|
|
const GskColorStop *color_stops,
|
|
gsize n_color_stops)
|
|
{
|
|
GskRadialGradientNode *self;
|
|
GskRenderNode *node;
|
|
gsize i;
|
|
|
|
g_return_val_if_fail (bounds != NULL, NULL);
|
|
g_return_val_if_fail (center != NULL, NULL);
|
|
g_return_val_if_fail (hradius > 0., NULL);
|
|
g_return_val_if_fail (vradius > 0., NULL);
|
|
g_return_val_if_fail (start >= 0., NULL);
|
|
g_return_val_if_fail (end >= 0., NULL);
|
|
g_return_val_if_fail (end > start, NULL);
|
|
g_return_val_if_fail (color_stops != NULL, NULL);
|
|
g_return_val_if_fail (n_color_stops >= 2, NULL);
|
|
g_return_val_if_fail (color_stops[0].offset >= 0, NULL);
|
|
for (i = 1; i < n_color_stops; i++)
|
|
g_return_val_if_fail (color_stops[i].offset >= color_stops[i - 1].offset, NULL);
|
|
g_return_val_if_fail (color_stops[n_color_stops - 1].offset <= 1, NULL);
|
|
|
|
self = gsk_render_node_alloc (GSK_REPEATING_RADIAL_GRADIENT_NODE);
|
|
node = (GskRenderNode *) self;
|
|
node->offscreen_for_opacity = FALSE;
|
|
node->fully_opaque = gsk_color_stops_are_opaque (color_stops, n_color_stops);
|
|
node->preferred_depth = my_color_stops_get_depth (color_stops, n_color_stops);
|
|
|
|
gsk_rect_init_from_rect (&node->bounds, bounds);
|
|
gsk_rect_normalize (&node->bounds);
|
|
graphene_point_init_from_point (&self->center, center);
|
|
|
|
self->hradius = hradius;
|
|
self->vradius = vradius;
|
|
self->start = start;
|
|
self->end = end;
|
|
|
|
self->n_stops = n_color_stops;
|
|
self->stops = g_malloc_n (n_color_stops, sizeof (GskColorStop));
|
|
memcpy (self->stops, color_stops, n_color_stops * sizeof (GskColorStop));
|
|
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* gsk_radial_gradient_node_get_n_color_stops:
|
|
* @node: (type GskRadialGradientNode): a `GskRenderNode` for a radial gradient
|
|
*
|
|
* Retrieves the number of color stops in the gradient.
|
|
*
|
|
* Returns: the number of color stops
|
|
*/
|
|
gsize
|
|
gsk_radial_gradient_node_get_n_color_stops (const GskRenderNode *node)
|
|
{
|
|
const GskRadialGradientNode *self = (const GskRadialGradientNode *) node;
|
|
|
|
return self->n_stops;
|
|
}
|
|
|
|
/**
|
|
* gsk_radial_gradient_node_get_color_stops:
|
|
* @node: (type GskRadialGradientNode): a `GskRenderNode` for a radial gradient
|
|
* @n_stops: (out) (optional): the number of color stops in the returned array
|
|
*
|
|
* Retrieves the color stops in the gradient.
|
|
*
|
|
* Returns: (array length=n_stops): the color stops in the gradient
|
|
*/
|
|
const GskColorStop *
|
|
gsk_radial_gradient_node_get_color_stops (const GskRenderNode *node,
|
|
gsize *n_stops)
|
|
{
|
|
const GskRadialGradientNode *self = (const GskRadialGradientNode *) node;
|
|
|
|
if (n_stops != NULL)
|
|
*n_stops = self->n_stops;
|
|
|
|
return self->stops;
|
|
}
|
|
|
|
/**
|
|
* gsk_radial_gradient_node_get_center:
|
|
* @node: (type GskRadialGradientNode): a `GskRenderNode` for a radial gradient
|
|
*
|
|
* Retrieves the center pointer for the gradient.
|
|
*
|
|
* Returns: the center point for the gradient
|
|
*/
|
|
const graphene_point_t *
|
|
gsk_radial_gradient_node_get_center (const GskRenderNode *node)
|
|
{
|
|
const GskRadialGradientNode *self = (const GskRadialGradientNode *) node;
|
|
|
|
return &self->center;
|
|
}
|
|
|
|
/**
|
|
* gsk_radial_gradient_node_get_hradius:
|
|
* @node: (type GskRadialGradientNode): a `GskRenderNode` for a radial gradient
|
|
*
|
|
* Retrieves the horizontal radius for the gradient.
|
|
*
|
|
* Returns: the horizontal radius for the gradient
|
|
*/
|
|
float
|
|
gsk_radial_gradient_node_get_hradius (const GskRenderNode *node)
|
|
{
|
|
const GskRadialGradientNode *self = (const GskRadialGradientNode *) node;
|
|
|
|
return self->hradius;
|
|
}
|
|
|
|
/**
|
|
* gsk_radial_gradient_node_get_vradius:
|
|
* @node: (type GskRadialGradientNode): a `GskRenderNode` for a radial gradient
|
|
*
|
|
* Retrieves the vertical radius for the gradient.
|
|
*
|
|
* Returns: the vertical radius for the gradient
|
|
*/
|
|
float
|
|
gsk_radial_gradient_node_get_vradius (const GskRenderNode *node)
|
|
{
|
|
const GskRadialGradientNode *self = (const GskRadialGradientNode *) node;
|
|
|
|
return self->vradius;
|
|
}
|
|
|
|
/**
|
|
* gsk_radial_gradient_node_get_start:
|
|
* @node: (type GskRadialGradientNode): a `GskRenderNode` for a radial gradient
|
|
*
|
|
* Retrieves the start value for the gradient.
|
|
*
|
|
* Returns: the start value for the gradient
|
|
*/
|
|
float
|
|
gsk_radial_gradient_node_get_start (const GskRenderNode *node)
|
|
{
|
|
const GskRadialGradientNode *self = (const GskRadialGradientNode *) node;
|
|
|
|
return self->start;
|
|
}
|
|
|
|
/**
|
|
* gsk_radial_gradient_node_get_end:
|
|
* @node: (type GskRadialGradientNode): a `GskRenderNode` for a radial gradient
|
|
*
|
|
* Retrieves the end value for the gradient.
|
|
*
|
|
* Returns: the end value for the gradient
|
|
*/
|
|
float
|
|
gsk_radial_gradient_node_get_end (const GskRenderNode *node)
|
|
{
|
|
const GskRadialGradientNode *self = (const GskRadialGradientNode *) node;
|
|
|
|
return self->end;
|
|
}
|
|
|
|
/* }}} */
|
|
/* {{{ GSK_CONIC_GRADIENT_NODE */
|
|
|
|
/**
|
|
* GskConicGradientNode:
|
|
*
|
|
* A render node for a conic gradient.
|
|
*/
|
|
|
|
struct _GskConicGradientNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
graphene_point_t center;
|
|
float rotation;
|
|
float angle;
|
|
|
|
gsize n_stops;
|
|
GskColorStop *stops;
|
|
};
|
|
|
|
static void
|
|
gsk_conic_gradient_node_finalize (GskRenderNode *node)
|
|
{
|
|
GskConicGradientNode *self = (GskConicGradientNode *) node;
|
|
GskRenderNodeClass *parent_class = g_type_class_peek (g_type_parent (GSK_TYPE_CONIC_GRADIENT_NODE));
|
|
|
|
g_free (self->stops);
|
|
|
|
parent_class->finalize (node);
|
|
}
|
|
|
|
#define DEG_TO_RAD(x) ((x) * (G_PI / 180.f))
|
|
|
|
static void
|
|
_cairo_mesh_pattern_set_corner_rgba (cairo_pattern_t *pattern,
|
|
guint corner_num,
|
|
const float color[4])
|
|
{
|
|
cairo_mesh_pattern_set_corner_color_rgba (pattern, corner_num, color[0], color[1], color[2], color[3]);
|
|
}
|
|
|
|
static void
|
|
project (double angle,
|
|
double radius,
|
|
double *x_out,
|
|
double *y_out)
|
|
{
|
|
double x, y;
|
|
|
|
#ifdef HAVE_SINCOS
|
|
sincos (angle, &y, &x);
|
|
#else
|
|
x = cos (angle);
|
|
y = sin (angle);
|
|
#endif
|
|
*x_out = radius * x;
|
|
*y_out = radius * y;
|
|
}
|
|
|
|
static void
|
|
gsk_conic_gradient_node_add_patch (cairo_pattern_t *pattern,
|
|
GdkColorState *ccs,
|
|
float radius,
|
|
float start_angle,
|
|
const GdkRGBA *start_color,
|
|
float end_angle,
|
|
const GdkRGBA *end_color)
|
|
{
|
|
double x, y;
|
|
float start[4], end[4];
|
|
|
|
cairo_mesh_pattern_begin_patch (pattern);
|
|
|
|
cairo_mesh_pattern_move_to (pattern, 0, 0);
|
|
project (start_angle, radius, &x, &y);
|
|
cairo_mesh_pattern_line_to (pattern, x, y);
|
|
project (end_angle, radius, &x, &y);
|
|
cairo_mesh_pattern_line_to (pattern, x, y);
|
|
cairo_mesh_pattern_line_to (pattern, 0, 0);
|
|
|
|
gdk_color_state_from_rgba (ccs, start_color, start);
|
|
_cairo_mesh_pattern_set_corner_rgba (pattern, 0, start);
|
|
_cairo_mesh_pattern_set_corner_rgba (pattern, 1, start);
|
|
gdk_color_state_from_rgba (ccs, end_color, end);
|
|
_cairo_mesh_pattern_set_corner_rgba (pattern, 2, end);
|
|
_cairo_mesh_pattern_set_corner_rgba (pattern, 3, end);
|
|
|
|
cairo_mesh_pattern_end_patch (pattern);
|
|
}
|
|
|
|
static void
|
|
gdk_rgba_color_interpolate (GdkRGBA *dest,
|
|
const GdkRGBA *src1,
|
|
const GdkRGBA *src2,
|
|
double progress)
|
|
{
|
|
double alpha = src1->alpha * (1.0 - progress) + src2->alpha * progress;
|
|
|
|
dest->alpha = alpha;
|
|
if (alpha == 0)
|
|
{
|
|
dest->red = src1->red * (1.0 - progress) + src2->red * progress;
|
|
dest->green = src1->green * (1.0 - progress) + src2->green * progress;
|
|
dest->blue = src1->blue * (1.0 - progress) + src2->blue * progress;
|
|
}
|
|
else
|
|
{
|
|
dest->red = (src1->red * src1->alpha * (1.0 - progress) + src2->red * src2->alpha * progress) / alpha;
|
|
dest->green = (src1->green * src1->alpha * (1.0 - progress) + src2->green * src2->alpha * progress) / alpha;
|
|
dest->blue = (src1->blue * src1->alpha * (1.0 - progress) + src2->blue * src2->alpha * progress) / alpha;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gsk_conic_gradient_node_draw (GskRenderNode *node,
|
|
cairo_t *cr,
|
|
GdkColorState *ccs)
|
|
{
|
|
GskConicGradientNode *self = (GskConicGradientNode *) node;
|
|
cairo_pattern_t *pattern;
|
|
graphene_point_t corner;
|
|
float radius;
|
|
gsize i;
|
|
|
|
pattern = cairo_pattern_create_mesh ();
|
|
graphene_rect_get_top_right (&node->bounds, &corner);
|
|
radius = graphene_point_distance (&self->center, &corner, NULL, NULL);
|
|
graphene_rect_get_bottom_right (&node->bounds, &corner);
|
|
radius = MAX (radius, graphene_point_distance (&self->center, &corner, NULL, NULL));
|
|
graphene_rect_get_bottom_left (&node->bounds, &corner);
|
|
radius = MAX (radius, graphene_point_distance (&self->center, &corner, NULL, NULL));
|
|
graphene_rect_get_top_left (&node->bounds, &corner);
|
|
radius = MAX (radius, graphene_point_distance (&self->center, &corner, NULL, NULL));
|
|
|
|
for (i = 0; i <= self->n_stops; i++)
|
|
{
|
|
GskColorStop *stop1 = &self->stops[MAX (i, 1) - 1];
|
|
GskColorStop *stop2 = &self->stops[MIN (i, self->n_stops - 1)];
|
|
double offset1 = i > 0 ? stop1->offset : 0;
|
|
double offset2 = i < self->n_stops ? stop2->offset : 1;
|
|
double start_angle, end_angle;
|
|
|
|
offset1 = offset1 * 360 + self->rotation - 90;
|
|
offset2 = offset2 * 360 + self->rotation - 90;
|
|
|
|
for (start_angle = offset1; start_angle < offset2; start_angle = end_angle)
|
|
{
|
|
GdkRGBA start_color, end_color;
|
|
end_angle = (floor (start_angle / 45) + 1) * 45;
|
|
end_angle = MIN (end_angle, offset2);
|
|
gdk_rgba_color_interpolate (&start_color,
|
|
&stop1->color,
|
|
&stop2->color,
|
|
(start_angle - offset1) / (offset2 - offset1));
|
|
gdk_rgba_color_interpolate (&end_color,
|
|
&stop1->color,
|
|
&stop2->color,
|
|
(end_angle - offset1) / (offset2 - offset1));
|
|
|
|
gsk_conic_gradient_node_add_patch (pattern,
|
|
ccs,
|
|
radius,
|
|
DEG_TO_RAD (start_angle),
|
|
&start_color,
|
|
DEG_TO_RAD (end_angle),
|
|
&end_color);
|
|
}
|
|
}
|
|
|
|
cairo_pattern_set_extend (pattern, CAIRO_EXTEND_PAD);
|
|
|
|
gdk_cairo_rect (cr, &node->bounds);
|
|
cairo_translate (cr, self->center.x, self->center.y);
|
|
cairo_set_source (cr, pattern);
|
|
cairo_fill (cr);
|
|
|
|
cairo_pattern_destroy (pattern);
|
|
}
|
|
|
|
static void
|
|
gsk_conic_gradient_node_diff (GskRenderNode *node1,
|
|
GskRenderNode *node2,
|
|
GskDiffData *data)
|
|
{
|
|
GskConicGradientNode *self1 = (GskConicGradientNode *) node1;
|
|
GskConicGradientNode *self2 = (GskConicGradientNode *) node2;
|
|
gsize i;
|
|
|
|
if (!graphene_point_equal (&self1->center, &self2->center) ||
|
|
self1->rotation != self2->rotation ||
|
|
self1->n_stops != self2->n_stops)
|
|
{
|
|
gsk_render_node_diff_impossible (node1, node2, data);
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < self1->n_stops; i++)
|
|
{
|
|
GskColorStop *stop1 = &self1->stops[i];
|
|
GskColorStop *stop2 = &self2->stops[i];
|
|
|
|
if (stop1->offset != stop2->offset ||
|
|
!gdk_rgba_equal (&stop1->color, &stop2->color))
|
|
{
|
|
gsk_render_node_diff_impossible (node1, node2, data);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
gsk_conic_gradient_node_class_init (gpointer g_class,
|
|
gpointer class_data)
|
|
{
|
|
GskRenderNodeClass *node_class = g_class;
|
|
|
|
node_class->node_type = GSK_CONIC_GRADIENT_NODE;
|
|
|
|
node_class->finalize = gsk_conic_gradient_node_finalize;
|
|
node_class->draw = gsk_conic_gradient_node_draw;
|
|
node_class->diff = gsk_conic_gradient_node_diff;
|
|
}
|
|
|
|
/**
|
|
* gsk_conic_gradient_node_new:
|
|
* @bounds: the bounds of the node
|
|
* @center: the center of the gradient
|
|
* @rotation: the rotation of the gradient in degrees
|
|
* @color_stops: (array length=n_color_stops): a pointer to an array of
|
|
* `GskColorStop` defining the gradient. The offsets of all color stops
|
|
* must be increasing. The first stop's offset must be >= 0 and the last
|
|
* stop's offset must be <= 1.
|
|
* @n_color_stops: the number of elements in @color_stops
|
|
*
|
|
* Creates a `GskRenderNode` that draws a conic gradient.
|
|
*
|
|
* The conic gradient
|
|
* starts around @center in the direction of @rotation. A rotation of 0 means
|
|
* that the gradient points up. Color stops are then added clockwise.
|
|
*
|
|
* Returns: (transfer full) (type GskConicGradientNode): A new `GskRenderNode`
|
|
*/
|
|
GskRenderNode *
|
|
gsk_conic_gradient_node_new (const graphene_rect_t *bounds,
|
|
const graphene_point_t *center,
|
|
float rotation,
|
|
const GskColorStop *color_stops,
|
|
gsize n_color_stops)
|
|
{
|
|
GskConicGradientNode *self;
|
|
GskRenderNode *node;
|
|
gsize i;
|
|
|
|
g_return_val_if_fail (bounds != NULL, NULL);
|
|
g_return_val_if_fail (center != NULL, NULL);
|
|
g_return_val_if_fail (color_stops != NULL, NULL);
|
|
g_return_val_if_fail (n_color_stops >= 2, NULL);
|
|
g_return_val_if_fail (color_stops[0].offset >= 0, NULL);
|
|
for (i = 1; i < n_color_stops; i++)
|
|
g_return_val_if_fail (color_stops[i].offset >= color_stops[i - 1].offset, NULL);
|
|
g_return_val_if_fail (color_stops[n_color_stops - 1].offset <= 1, NULL);
|
|
|
|
self = gsk_render_node_alloc (GSK_CONIC_GRADIENT_NODE);
|
|
node = (GskRenderNode *) self;
|
|
node->offscreen_for_opacity = FALSE;
|
|
node->fully_opaque = gsk_color_stops_are_opaque (color_stops, n_color_stops);
|
|
node->preferred_depth = my_color_stops_get_depth (color_stops, n_color_stops);
|
|
|
|
gsk_rect_init_from_rect (&node->bounds, bounds);
|
|
gsk_rect_normalize (&node->bounds);
|
|
graphene_point_init_from_point (&self->center, center);
|
|
|
|
self->rotation = rotation;
|
|
|
|
self->n_stops = n_color_stops;
|
|
self->stops = g_malloc_n (n_color_stops, sizeof (GskColorStop));
|
|
memcpy (self->stops, color_stops, n_color_stops * sizeof (GskColorStop));
|
|
|
|
self->angle = 90.f - self->rotation;
|
|
self->angle = G_PI * self->angle / 180.f;
|
|
self->angle = fmodf (self->angle, 2.f * G_PI);
|
|
if (self->angle < 0.f)
|
|
self->angle += 2.f * G_PI;
|
|
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* gsk_conic_gradient_node_get_n_color_stops:
|
|
* @node: (type GskConicGradientNode): a `GskRenderNode` for a conic gradient
|
|
*
|
|
* Retrieves the number of color stops in the gradient.
|
|
*
|
|
* Returns: the number of color stops
|
|
*/
|
|
gsize
|
|
gsk_conic_gradient_node_get_n_color_stops (const GskRenderNode *node)
|
|
{
|
|
const GskConicGradientNode *self = (const GskConicGradientNode *) node;
|
|
|
|
return self->n_stops;
|
|
}
|
|
|
|
/**
|
|
* gsk_conic_gradient_node_get_color_stops:
|
|
* @node: (type GskConicGradientNode): a `GskRenderNode` for a conic gradient
|
|
* @n_stops: (out) (optional): the number of color stops in the returned array
|
|
*
|
|
* Retrieves the color stops in the gradient.
|
|
*
|
|
* Returns: (array length=n_stops): the color stops in the gradient
|
|
*/
|
|
const GskColorStop *
|
|
gsk_conic_gradient_node_get_color_stops (const GskRenderNode *node,
|
|
gsize *n_stops)
|
|
{
|
|
const GskConicGradientNode *self = (const GskConicGradientNode *) node;
|
|
|
|
if (n_stops != NULL)
|
|
*n_stops = self->n_stops;
|
|
|
|
return self->stops;
|
|
}
|
|
|
|
/**
|
|
* gsk_conic_gradient_node_get_center:
|
|
* @node: (type GskConicGradientNode): a `GskRenderNode` for a conic gradient
|
|
*
|
|
* Retrieves the center pointer for the gradient.
|
|
*
|
|
* Returns: the center point for the gradient
|
|
*/
|
|
const graphene_point_t *
|
|
gsk_conic_gradient_node_get_center (const GskRenderNode *node)
|
|
{
|
|
const GskConicGradientNode *self = (const GskConicGradientNode *) node;
|
|
|
|
return &self->center;
|
|
}
|
|
|
|
/**
|
|
* gsk_conic_gradient_node_get_rotation:
|
|
* @node: (type GskConicGradientNode): a `GskRenderNode` for a conic gradient
|
|
*
|
|
* Retrieves the rotation for the gradient in degrees.
|
|
*
|
|
* Returns: the rotation for the gradient
|
|
*/
|
|
float
|
|
gsk_conic_gradient_node_get_rotation (const GskRenderNode *node)
|
|
{
|
|
const GskConicGradientNode *self = (const GskConicGradientNode *) node;
|
|
|
|
return self->rotation;
|
|
}
|
|
|
|
/**
|
|
* gsk_conic_gradient_node_get_angle:
|
|
* @node: (type GskConicGradientNode): a `GskRenderNode` for a conic gradient
|
|
*
|
|
* Retrieves the angle for the gradient in radians, normalized in [0, 2 * PI].
|
|
*
|
|
* The angle is starting at the top and going clockwise, as expressed
|
|
* in the css specification:
|
|
*
|
|
* angle = 90 - gsk_conic_gradient_node_get_rotation()
|
|
*
|
|
* Returns: the angle for the gradient
|
|
*
|
|
* Since: 4.2
|
|
*/
|
|
float
|
|
gsk_conic_gradient_node_get_angle (const GskRenderNode *node)
|
|
{
|
|
const GskConicGradientNode *self = (const GskConicGradientNode *) node;
|
|
|
|
return self->angle;
|
|
}
|
|
|
|
/* }}} */
|
|
/* {{{ GSK_BORDER_NODE */
|
|
|
|
/**
|
|
* GskBorderNode:
|
|
*
|
|
* A render node for a border.
|
|
*/
|
|
struct _GskBorderNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
bool uniform_width: 1;
|
|
bool uniform_color: 1;
|
|
GskRoundedRect outline;
|
|
float border_width[4];
|
|
GdkRGBA border_color[4];
|
|
};
|
|
|
|
static void
|
|
gsk_border_node_mesh_add_patch (cairo_pattern_t *pattern,
|
|
GdkColorState *ccs,
|
|
const GdkRGBA *rgba,
|
|
double x0,
|
|
double y0,
|
|
double x1,
|
|
double y1,
|
|
double x2,
|
|
double y2,
|
|
double x3,
|
|
double y3)
|
|
{
|
|
float color[4];
|
|
|
|
gdk_color_state_from_rgba (ccs, rgba, color);
|
|
cairo_mesh_pattern_begin_patch (pattern);
|
|
cairo_mesh_pattern_move_to (pattern, x0, y0);
|
|
cairo_mesh_pattern_line_to (pattern, x1, y1);
|
|
cairo_mesh_pattern_line_to (pattern, x2, y2);
|
|
cairo_mesh_pattern_line_to (pattern, x3, y3);
|
|
cairo_mesh_pattern_set_corner_color_rgba (pattern, 0, color[0], color[1], color[2], color[3]);
|
|
cairo_mesh_pattern_set_corner_color_rgba (pattern, 1, color[0], color[1], color[2], color[3]);
|
|
cairo_mesh_pattern_set_corner_color_rgba (pattern, 2, color[0], color[1], color[2], color[3]);
|
|
cairo_mesh_pattern_set_corner_color_rgba (pattern, 3, color[0], color[1], color[2], color[3]);
|
|
cairo_mesh_pattern_end_patch (pattern);
|
|
}
|
|
|
|
static void
|
|
gsk_border_node_draw (GskRenderNode *node,
|
|
cairo_t *cr,
|
|
GdkColorState *ccs)
|
|
{
|
|
GskBorderNode *self = (GskBorderNode *) node;
|
|
GskRoundedRect inside;
|
|
|
|
cairo_save (cr);
|
|
|
|
gsk_rounded_rect_init_copy (&inside, &self->outline);
|
|
gsk_rounded_rect_shrink (&inside,
|
|
self->border_width[0], self->border_width[1],
|
|
self->border_width[2], self->border_width[3]);
|
|
|
|
cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);
|
|
gsk_rounded_rect_path (&self->outline, cr);
|
|
gsk_rounded_rect_path (&inside, cr);
|
|
|
|
if (gdk_rgba_equal (&self->border_color[0], &self->border_color[1]) &&
|
|
gdk_rgba_equal (&self->border_color[0], &self->border_color[2]) &&
|
|
gdk_rgba_equal (&self->border_color[0], &self->border_color[3]))
|
|
{
|
|
gdk_cairo_set_source_rgba_ccs (cr, ccs, &self->border_color[0]);
|
|
}
|
|
else
|
|
{
|
|
const graphene_rect_t *bounds = &self->outline.bounds;
|
|
/* distance to center "line":
|
|
* +-------------------------+
|
|
* | |
|
|
* | |
|
|
* | ---this-line--- |
|
|
* | |
|
|
* | |
|
|
* +-------------------------+
|
|
* That line is equidistant from all sides. It's either horizontal
|
|
* or vertical, depending on if the rect is wider or taller.
|
|
* We use the 4 sides spanned up by connecting the line to the corner
|
|
* points to color the regions of the rectangle differently.
|
|
* Note that the call to cairo_fill() will add the potential final
|
|
* segment by closing the path, so we don't have to care.
|
|
*/
|
|
cairo_pattern_t *mesh;
|
|
cairo_matrix_t mat;
|
|
graphene_point_t tl, br;
|
|
float scale;
|
|
|
|
mesh = cairo_pattern_create_mesh ();
|
|
cairo_matrix_init_translate (&mat, -bounds->origin.x, -bounds->origin.y);
|
|
cairo_pattern_set_matrix (mesh, &mat);
|
|
|
|
scale = MIN (bounds->size.width / (self->border_width[1] + self->border_width[3]),
|
|
bounds->size.height / (self->border_width[0] + self->border_width[2]));
|
|
graphene_point_init (&tl,
|
|
self->border_width[3] * scale,
|
|
self->border_width[0] * scale);
|
|
graphene_point_init (&br,
|
|
bounds->size.width - self->border_width[1] * scale,
|
|
bounds->size.height - self->border_width[2] * scale);
|
|
|
|
/* Top */
|
|
if (self->border_width[0] > 0)
|
|
{
|
|
gsk_border_node_mesh_add_patch (mesh,
|
|
ccs,
|
|
&self->border_color[0],
|
|
0, 0,
|
|
tl.x, tl.y,
|
|
br.x, tl.y,
|
|
bounds->size.width, 0);
|
|
}
|
|
|
|
/* Right */
|
|
if (self->border_width[1] > 0)
|
|
{
|
|
gsk_border_node_mesh_add_patch (mesh,
|
|
ccs,
|
|
&self->border_color[1],
|
|
bounds->size.width, 0,
|
|
br.x, tl.y,
|
|
br.x, br.y,
|
|
bounds->size.width, bounds->size.height);
|
|
}
|
|
|
|
/* Bottom */
|
|
if (self->border_width[2] > 0)
|
|
{
|
|
gsk_border_node_mesh_add_patch (mesh,
|
|
ccs,
|
|
&self->border_color[2],
|
|
0, bounds->size.height,
|
|
tl.x, br.y,
|
|
br.x, br.y,
|
|
bounds->size.width, bounds->size.height);
|
|
}
|
|
|
|
/* Left */
|
|
if (self->border_width[3] > 0)
|
|
{
|
|
gsk_border_node_mesh_add_patch (mesh,
|
|
ccs,
|
|
&self->border_color[3],
|
|
0, 0,
|
|
tl.x, tl.y,
|
|
tl.x, br.y,
|
|
0, bounds->size.height);
|
|
}
|
|
|
|
cairo_set_source (cr, mesh);
|
|
cairo_pattern_destroy (mesh);
|
|
}
|
|
|
|
cairo_fill (cr);
|
|
cairo_restore (cr);
|
|
}
|
|
|
|
static void
|
|
gsk_border_node_diff (GskRenderNode *node1,
|
|
GskRenderNode *node2,
|
|
GskDiffData *data)
|
|
{
|
|
GskBorderNode *self1 = (GskBorderNode *) node1;
|
|
GskBorderNode *self2 = (GskBorderNode *) node2;
|
|
gboolean uniform1 = self1->uniform_width && self1->uniform_color;
|
|
gboolean uniform2 = self2->uniform_width && self2->uniform_color;
|
|
|
|
if (uniform1 &&
|
|
uniform2 &&
|
|
self1->border_width[0] == self2->border_width[0] &&
|
|
gsk_rounded_rect_equal (&self1->outline, &self2->outline) &&
|
|
gdk_rgba_equal (&self1->border_color[0], &self2->border_color[0]))
|
|
return;
|
|
|
|
/* Different uniformity -> diff impossible */
|
|
if (uniform1 ^ uniform2)
|
|
{
|
|
gsk_render_node_diff_impossible (node1, node2, data);
|
|
return;
|
|
}
|
|
|
|
if (self1->border_width[0] == self2->border_width[0] &&
|
|
self1->border_width[1] == self2->border_width[1] &&
|
|
self1->border_width[2] == self2->border_width[2] &&
|
|
self1->border_width[3] == self2->border_width[3] &&
|
|
gdk_rgba_equal (&self1->border_color[0], &self2->border_color[0]) &&
|
|
gdk_rgba_equal (&self1->border_color[1], &self2->border_color[1]) &&
|
|
gdk_rgba_equal (&self1->border_color[2], &self2->border_color[2]) &&
|
|
gdk_rgba_equal (&self1->border_color[3], &self2->border_color[3]) &&
|
|
gsk_rounded_rect_equal (&self1->outline, &self2->outline))
|
|
return;
|
|
|
|
gsk_render_node_diff_impossible (node1, node2, data);
|
|
}
|
|
|
|
static void
|
|
gsk_border_node_class_init (gpointer g_class,
|
|
gpointer class_data)
|
|
{
|
|
GskRenderNodeClass *node_class = g_class;
|
|
|
|
node_class->node_type = GSK_BORDER_NODE;
|
|
|
|
node_class->draw = gsk_border_node_draw;
|
|
node_class->diff = gsk_border_node_diff;
|
|
}
|
|
|
|
/**
|
|
* gsk_border_node_get_outline:
|
|
* @node: (type GskBorderNode): a `GskRenderNode` for a border
|
|
*
|
|
* Retrieves the outline of the border.
|
|
*
|
|
* Returns: the outline of the border
|
|
*/
|
|
const GskRoundedRect *
|
|
gsk_border_node_get_outline (const GskRenderNode *node)
|
|
{
|
|
const GskBorderNode *self = (const GskBorderNode *) node;
|
|
|
|
return &self->outline;
|
|
}
|
|
|
|
/**
|
|
* gsk_border_node_get_widths:
|
|
* @node: (type GskBorderNode): a `GskRenderNode` for a border
|
|
*
|
|
* Retrieves the stroke widths of the border.
|
|
*
|
|
* Returns: (transfer none) (array fixed-size=4): an array of 4 floats
|
|
* for the top, right, bottom and left stroke width of the border,
|
|
* respectively
|
|
*/
|
|
const float *
|
|
gsk_border_node_get_widths (const GskRenderNode *node)
|
|
{
|
|
const GskBorderNode *self = (const GskBorderNode *) node;
|
|
|
|
return self->border_width;
|
|
}
|
|
|
|
/**
|
|
* gsk_border_node_get_colors:
|
|
* @node: (type GskBorderNode): a `GskRenderNode` for a border
|
|
*
|
|
* Retrieves the colors of the border.
|
|
*
|
|
* Returns: (transfer none): an array of 4 `GdkRGBA` structs
|
|
* for the top, right, bottom and left color of the border
|
|
*/
|
|
const GdkRGBA *
|
|
gsk_border_node_get_colors (const GskRenderNode *node)
|
|
{
|
|
const GskBorderNode *self = (const GskBorderNode *) node;
|
|
|
|
return self->border_color;
|
|
}
|
|
|
|
/**
|
|
* gsk_border_node_new:
|
|
* @outline: a `GskRoundedRect` describing the outline of the border
|
|
* @border_width: (array fixed-size=4): the stroke width of the border on
|
|
* the top, right, bottom and left side respectively.
|
|
* @border_color: (array fixed-size=4): the color used on the top, right,
|
|
* bottom and left side.
|
|
*
|
|
* Creates a `GskRenderNode` that will stroke a border rectangle inside the
|
|
* given @outline.
|
|
*
|
|
* The 4 sides of the border can have different widths and colors.
|
|
*
|
|
* Returns: (transfer full) (type GskBorderNode): A new `GskRenderNode`
|
|
*/
|
|
GskRenderNode *
|
|
gsk_border_node_new (const GskRoundedRect *outline,
|
|
const float border_width[4],
|
|
const GdkRGBA border_color[4])
|
|
{
|
|
GskBorderNode *self;
|
|
GskRenderNode *node;
|
|
|
|
g_return_val_if_fail (outline != NULL, NULL);
|
|
g_return_val_if_fail (border_width != NULL, NULL);
|
|
g_return_val_if_fail (border_color != NULL, NULL);
|
|
|
|
self = gsk_render_node_alloc (GSK_BORDER_NODE);
|
|
node = (GskRenderNode *) self;
|
|
node->offscreen_for_opacity = FALSE;
|
|
node->preferred_depth = gdk_memory_depth_merge (
|
|
gdk_memory_depth_merge (my_color_get_depth (&border_color[0]),
|
|
my_color_get_depth (&border_color[1])),
|
|
gdk_memory_depth_merge (my_color_get_depth (&border_color[2]),
|
|
my_color_get_depth (&border_color[3])));
|
|
|
|
gsk_rounded_rect_init_copy (&self->outline, outline);
|
|
memcpy (self->border_width, border_width, sizeof (self->border_width));
|
|
memcpy (self->border_color, border_color, sizeof (self->border_color));
|
|
|
|
if (border_width[0] == border_width[1] &&
|
|
border_width[0] == border_width[2] &&
|
|
border_width[0] == border_width[3])
|
|
self->uniform_width = TRUE;
|
|
else
|
|
self->uniform_width = FALSE;
|
|
|
|
if (gdk_rgba_equal (&border_color[0], &border_color[1]) &&
|
|
gdk_rgba_equal (&border_color[0], &border_color[2]) &&
|
|
gdk_rgba_equal (&border_color[0], &border_color[3]))
|
|
self->uniform_color = TRUE;
|
|
else
|
|
self->uniform_color = FALSE;
|
|
|
|
gsk_rect_init_from_rect (&node->bounds, &self->outline.bounds);
|
|
|
|
return node;
|
|
}
|
|
|
|
/* Private */
|
|
bool
|
|
gsk_border_node_get_uniform (const GskRenderNode *self)
|
|
{
|
|
const GskBorderNode *node = (const GskBorderNode *)self;
|
|
return node->uniform_width && node->uniform_color;
|
|
}
|
|
|
|
bool
|
|
gsk_border_node_get_uniform_color (const GskRenderNode *self)
|
|
{
|
|
const GskBorderNode *node = (const GskBorderNode *)self;
|
|
return node->uniform_color;
|
|
}
|
|
|
|
/* }}} */
|
|
/* {{{ GSK_TEXTURE_NODE */
|
|
|
|
/**
|
|
* GskTextureNode:
|
|
*
|
|
* A render node for a `GdkTexture`.
|
|
*/
|
|
struct _GskTextureNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
GdkTexture *texture;
|
|
};
|
|
|
|
static void
|
|
gsk_texture_node_finalize (GskRenderNode *node)
|
|
{
|
|
GskTextureNode *self = (GskTextureNode *) node;
|
|
GskRenderNodeClass *parent_class = g_type_class_peek (g_type_parent (GSK_TYPE_TEXTURE_NODE));
|
|
|
|
g_clear_object (&self->texture);
|
|
|
|
parent_class->finalize (node);
|
|
}
|
|
|
|
static void
|
|
gsk_texture_node_draw_oversized (GskRenderNode *node,
|
|
cairo_t *cr,
|
|
GdkColorState *ccs)
|
|
{
|
|
GskTextureNode *self = (GskTextureNode *) node;
|
|
cairo_surface_t *surface;
|
|
int x, y, width, height;
|
|
GdkTextureDownloader downloader;
|
|
GBytes *bytes;
|
|
const guchar *data;
|
|
gsize stride;
|
|
|
|
width = gdk_texture_get_width (self->texture);
|
|
height = gdk_texture_get_height (self->texture);
|
|
gdk_texture_downloader_init (&downloader, self->texture);
|
|
gdk_texture_downloader_set_format (&downloader, GDK_MEMORY_DEFAULT);
|
|
bytes = gdk_texture_downloader_download_bytes (&downloader, &stride);
|
|
gdk_texture_downloader_finish (&downloader);
|
|
data = g_bytes_get_data (bytes, NULL);
|
|
gdk_memory_convert_color_state ((guchar *) data,
|
|
stride,
|
|
GDK_MEMORY_DEFAULT,
|
|
GDK_COLOR_STATE_SRGB,
|
|
ccs,
|
|
width, height);
|
|
|
|
gsk_cairo_rectangle_pixel_aligned (cr, &node->bounds);
|
|
cairo_clip (cr);
|
|
|
|
cairo_push_group (cr);
|
|
cairo_set_operator (cr, CAIRO_OPERATOR_ADD);
|
|
cairo_translate (cr,
|
|
node->bounds.origin.x,
|
|
node->bounds.origin.y);
|
|
cairo_scale (cr,
|
|
node->bounds.size.width / width,
|
|
node->bounds.size.height / height);
|
|
|
|
for (x = 0; x < width; x += MAX_CAIRO_IMAGE_WIDTH)
|
|
{
|
|
int tile_width = MIN (MAX_CAIRO_IMAGE_WIDTH, width - x);
|
|
for (y = 0; y < height; y += MAX_CAIRO_IMAGE_HEIGHT)
|
|
{
|
|
int tile_height = MIN (MAX_CAIRO_IMAGE_HEIGHT, height - y);
|
|
surface = cairo_image_surface_create_for_data ((guchar *) data + stride * y + 4 * x,
|
|
CAIRO_FORMAT_ARGB32,
|
|
tile_width, tile_height,
|
|
stride);
|
|
|
|
cairo_set_source_surface (cr, surface, x, y);
|
|
cairo_pattern_set_extend (cairo_get_source (cr), CAIRO_EXTEND_PAD);
|
|
cairo_rectangle (cr, x, y, tile_width, tile_height);
|
|
cairo_fill (cr);
|
|
|
|
cairo_surface_finish (surface);
|
|
cairo_surface_destroy (surface);
|
|
}
|
|
}
|
|
|
|
cairo_pop_group_to_source (cr);
|
|
cairo_paint (cr);
|
|
}
|
|
|
|
static void
|
|
gsk_texture_node_draw (GskRenderNode *node,
|
|
cairo_t *cr,
|
|
GdkColorState *ccs)
|
|
{
|
|
GskTextureNode *self = (GskTextureNode *) node;
|
|
cairo_surface_t *surface;
|
|
cairo_pattern_t *pattern;
|
|
cairo_matrix_t matrix;
|
|
int width, height;
|
|
|
|
width = gdk_texture_get_width (self->texture);
|
|
height = gdk_texture_get_height (self->texture);
|
|
if (width > MAX_CAIRO_IMAGE_WIDTH || height > MAX_CAIRO_IMAGE_HEIGHT)
|
|
{
|
|
gsk_texture_node_draw_oversized (node, cr, ccs);
|
|
return;
|
|
}
|
|
|
|
surface = gdk_texture_download_surface (self->texture, ccs);
|
|
pattern = cairo_pattern_create_for_surface (surface);
|
|
cairo_pattern_set_extend (pattern, CAIRO_EXTEND_PAD);
|
|
|
|
cairo_matrix_init_scale (&matrix,
|
|
width / node->bounds.size.width,
|
|
height / node->bounds.size.height);
|
|
cairo_matrix_translate (&matrix,
|
|
-node->bounds.origin.x,
|
|
-node->bounds.origin.y);
|
|
cairo_pattern_set_matrix (pattern, &matrix);
|
|
|
|
cairo_set_source (cr, pattern);
|
|
cairo_pattern_destroy (pattern);
|
|
cairo_surface_destroy (surface);
|
|
|
|
gdk_cairo_rect (cr, &node->bounds);
|
|
cairo_fill (cr);
|
|
}
|
|
|
|
static void
|
|
gsk_texture_node_diff (GskRenderNode *node1,
|
|
GskRenderNode *node2,
|
|
GskDiffData *data)
|
|
{
|
|
GskTextureNode *self1 = (GskTextureNode *) node1;
|
|
GskTextureNode *self2 = (GskTextureNode *) node2;
|
|
cairo_region_t *sub;
|
|
|
|
if (!gsk_rect_equal (&node1->bounds, &node2->bounds) ||
|
|
gdk_texture_get_width (self1->texture) != gdk_texture_get_width (self2->texture) ||
|
|
gdk_texture_get_height (self1->texture) != gdk_texture_get_height (self2->texture))
|
|
{
|
|
gsk_render_node_diff_impossible (node1, node2, data);
|
|
return;
|
|
}
|
|
|
|
if (self1->texture == self2->texture)
|
|
return;
|
|
|
|
sub = cairo_region_create ();
|
|
gdk_texture_diff (self1->texture, self2->texture, sub);
|
|
region_union_region_affine (data->region,
|
|
sub,
|
|
node1->bounds.size.width / gdk_texture_get_width (self1->texture),
|
|
node1->bounds.size.height / gdk_texture_get_height (self1->texture),
|
|
node1->bounds.origin.x,
|
|
node1->bounds.origin.y);
|
|
cairo_region_destroy (sub);
|
|
}
|
|
|
|
static void
|
|
gsk_texture_node_class_init (gpointer g_class,
|
|
gpointer class_data)
|
|
{
|
|
GskRenderNodeClass *node_class = g_class;
|
|
|
|
node_class->node_type = GSK_TEXTURE_NODE;
|
|
|
|
node_class->finalize = gsk_texture_node_finalize;
|
|
node_class->draw = gsk_texture_node_draw;
|
|
node_class->diff = gsk_texture_node_diff;
|
|
}
|
|
|
|
/**
|
|
* gsk_texture_node_get_texture:
|
|
* @node: (type GskTextureNode): a `GskRenderNode` of type %GSK_TEXTURE_NODE
|
|
*
|
|
* Retrieves the `GdkTexture` used when creating this `GskRenderNode`.
|
|
*
|
|
* Returns: (transfer none): the `GdkTexture`
|
|
*/
|
|
GdkTexture *
|
|
gsk_texture_node_get_texture (const GskRenderNode *node)
|
|
{
|
|
const GskTextureNode *self = (const GskTextureNode *) node;
|
|
|
|
return self->texture;
|
|
}
|
|
|
|
/**
|
|
* gsk_texture_node_new:
|
|
* @texture: the `GdkTexture`
|
|
* @bounds: the rectangle to render the texture into
|
|
*
|
|
* Creates a `GskRenderNode` that will render the given
|
|
* @texture into the area given by @bounds.
|
|
*
|
|
* Note that GSK applies linear filtering when textures are
|
|
* scaled and transformed. See [class@Gsk.TextureScaleNode]
|
|
* for a way to influence filtering.
|
|
*
|
|
* Returns: (transfer full) (type GskTextureNode): A new `GskRenderNode`
|
|
*/
|
|
GskRenderNode *
|
|
gsk_texture_node_new (GdkTexture *texture,
|
|
const graphene_rect_t *bounds)
|
|
{
|
|
GskTextureNode *self;
|
|
GskRenderNode *node;
|
|
|
|
g_return_val_if_fail (GDK_IS_TEXTURE (texture), NULL);
|
|
g_return_val_if_fail (bounds != NULL, NULL);
|
|
|
|
self = gsk_render_node_alloc (GSK_TEXTURE_NODE);
|
|
node = (GskRenderNode *) self;
|
|
node->offscreen_for_opacity = FALSE;
|
|
node->fully_opaque = gdk_memory_format_alpha (gdk_texture_get_format (texture)) == GDK_MEMORY_ALPHA_OPAQUE;
|
|
|
|
self->texture = g_object_ref (texture);
|
|
gsk_rect_init_from_rect (&node->bounds, bounds);
|
|
gsk_rect_normalize (&node->bounds);
|
|
|
|
node->preferred_depth = gdk_texture_get_depth (texture);
|
|
|
|
return node;
|
|
}
|
|
|
|
/* }}} */
|
|
/* {{{ GSK_TEXTURE_SCALE_NODE */
|
|
|
|
/**
|
|
* GskTextureScaleNode:
|
|
*
|
|
* A render node for a `GdkTexture`.
|
|
*
|
|
* Since: 4.10
|
|
*/
|
|
struct _GskTextureScaleNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
GdkTexture *texture;
|
|
GskScalingFilter filter;
|
|
};
|
|
|
|
static void
|
|
gsk_texture_scale_node_finalize (GskRenderNode *node)
|
|
{
|
|
GskTextureScaleNode *self = (GskTextureScaleNode *) node;
|
|
GskRenderNodeClass *parent_class = g_type_class_peek (g_type_parent (GSK_TYPE_TEXTURE_SCALE_NODE));
|
|
|
|
g_clear_object (&self->texture);
|
|
|
|
parent_class->finalize (node);
|
|
}
|
|
|
|
static void
|
|
gsk_texture_scale_node_draw (GskRenderNode *node,
|
|
cairo_t *cr,
|
|
GdkColorState *ccs)
|
|
{
|
|
GskTextureScaleNode *self = (GskTextureScaleNode *) node;
|
|
cairo_surface_t *surface;
|
|
cairo_pattern_t *pattern;
|
|
cairo_matrix_t matrix;
|
|
cairo_filter_t filters[] = {
|
|
CAIRO_FILTER_BILINEAR,
|
|
CAIRO_FILTER_NEAREST,
|
|
CAIRO_FILTER_GOOD,
|
|
};
|
|
cairo_t *cr2;
|
|
cairo_surface_t *surface2;
|
|
graphene_rect_t clip_rect;
|
|
|
|
/* Make sure we draw the minimum region by using the clip */
|
|
gdk_cairo_rect (cr, &node->bounds);
|
|
cairo_clip (cr);
|
|
_graphene_rect_init_from_clip_extents (&clip_rect, cr);
|
|
if (clip_rect.size.width <= 0 || clip_rect.size.height <= 0)
|
|
return;
|
|
|
|
surface2 = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
|
|
(int) ceilf (clip_rect.size.width),
|
|
(int) ceilf (clip_rect.size.height));
|
|
cairo_surface_set_device_offset (surface2, -clip_rect.origin.x, -clip_rect.origin.y);
|
|
cr2 = cairo_create (surface2);
|
|
|
|
surface = gdk_texture_download_surface (self->texture, ccs);
|
|
pattern = cairo_pattern_create_for_surface (surface);
|
|
cairo_pattern_set_extend (pattern, CAIRO_EXTEND_PAD);
|
|
|
|
cairo_matrix_init_scale (&matrix,
|
|
gdk_texture_get_width (self->texture) / node->bounds.size.width,
|
|
gdk_texture_get_height (self->texture) / node->bounds.size.height);
|
|
cairo_matrix_translate (&matrix, -node->bounds.origin.x, -node->bounds.origin.y);
|
|
cairo_pattern_set_matrix (pattern, &matrix);
|
|
cairo_pattern_set_filter (pattern, filters[self->filter]);
|
|
|
|
cairo_set_source (cr2, pattern);
|
|
cairo_pattern_destroy (pattern);
|
|
cairo_surface_destroy (surface);
|
|
|
|
gdk_cairo_rect (cr2, &node->bounds);
|
|
cairo_fill (cr2);
|
|
|
|
cairo_destroy (cr2);
|
|
|
|
cairo_save (cr);
|
|
|
|
cairo_set_source_surface (cr, surface2, 0, 0);
|
|
cairo_pattern_set_extend (cairo_get_source (cr), CAIRO_EXTEND_PAD);
|
|
cairo_surface_destroy (surface2);
|
|
|
|
cairo_paint (cr);
|
|
|
|
cairo_restore (cr);
|
|
}
|
|
|
|
static void
|
|
gsk_texture_scale_node_diff (GskRenderNode *node1,
|
|
GskRenderNode *node2,
|
|
GskDiffData *data)
|
|
{
|
|
GskTextureScaleNode *self1 = (GskTextureScaleNode *) node1;
|
|
GskTextureScaleNode *self2 = (GskTextureScaleNode *) node2;
|
|
cairo_region_t *sub;
|
|
|
|
if (!gsk_rect_equal (&node1->bounds, &node2->bounds) ||
|
|
self1->filter != self2->filter ||
|
|
gdk_texture_get_width (self1->texture) != gdk_texture_get_width (self2->texture) ||
|
|
gdk_texture_get_height (self1->texture) != gdk_texture_get_height (self2->texture))
|
|
{
|
|
gsk_render_node_diff_impossible (node1, node2, data);
|
|
return;
|
|
}
|
|
|
|
if (self1->texture == self2->texture)
|
|
return;
|
|
|
|
sub = cairo_region_create ();
|
|
gdk_texture_diff (self1->texture, self2->texture, sub);
|
|
region_union_region_affine (data->region,
|
|
sub,
|
|
node1->bounds.size.width / gdk_texture_get_width (self1->texture),
|
|
node1->bounds.size.height / gdk_texture_get_height (self1->texture),
|
|
node1->bounds.origin.x,
|
|
node1->bounds.origin.y);
|
|
cairo_region_destroy (sub);
|
|
}
|
|
|
|
static void
|
|
gsk_texture_scale_node_class_init (gpointer g_class,
|
|
gpointer class_data)
|
|
{
|
|
GskRenderNodeClass *node_class = g_class;
|
|
|
|
node_class->node_type = GSK_TEXTURE_SCALE_NODE;
|
|
|
|
node_class->finalize = gsk_texture_scale_node_finalize;
|
|
node_class->draw = gsk_texture_scale_node_draw;
|
|
node_class->diff = gsk_texture_scale_node_diff;
|
|
}
|
|
|
|
/**
|
|
* gsk_texture_scale_node_get_texture:
|
|
* @node: (type GskTextureScaleNode): a `GskRenderNode` of type %GSK_TEXTURE_SCALE_NODE
|
|
*
|
|
* Retrieves the `GdkTexture` used when creating this `GskRenderNode`.
|
|
*
|
|
* Returns: (transfer none): the `GdkTexture`
|
|
*
|
|
* Since: 4.10
|
|
*/
|
|
GdkTexture *
|
|
gsk_texture_scale_node_get_texture (const GskRenderNode *node)
|
|
{
|
|
const GskTextureScaleNode *self = (const GskTextureScaleNode *) node;
|
|
|
|
return self->texture;
|
|
}
|
|
|
|
/**
|
|
* gsk_texture_scale_node_get_filter:
|
|
* @node: (type GskTextureScaleNode): a `GskRenderNode` of type %GSK_TEXTURE_SCALE_NODE
|
|
*
|
|
* Retrieves the `GskScalingFilter` used when creating this `GskRenderNode`.
|
|
*
|
|
* Returns: (transfer none): the `GskScalingFilter`
|
|
*
|
|
* Since: 4.10
|
|
*/
|
|
GskScalingFilter
|
|
gsk_texture_scale_node_get_filter (const GskRenderNode *node)
|
|
{
|
|
const GskTextureScaleNode *self = (const GskTextureScaleNode *) node;
|
|
|
|
return self->filter;
|
|
}
|
|
|
|
/**
|
|
* gsk_texture_scale_node_new:
|
|
* @texture: the texture to scale
|
|
* @bounds: the size of the texture to scale to
|
|
* @filter: how to scale the texture
|
|
*
|
|
* Creates a node that scales the texture to the size given by the
|
|
* bounds using the filter and then places it at the bounds' position.
|
|
*
|
|
* Note that further scaling and other transformations which are
|
|
* applied to the node will apply linear filtering to the resulting
|
|
* texture, as usual.
|
|
*
|
|
* This node is intended for tight control over scaling applied
|
|
* to a texture, such as in image editors and requires the
|
|
* application to be aware of the whole render tree as further
|
|
* transforms may be applied that conflict with the desired effect
|
|
* of this node.
|
|
*
|
|
* Returns: (transfer full) (type GskTextureScaleNode): A new `GskRenderNode`
|
|
*
|
|
* Since: 4.10
|
|
*/
|
|
GskRenderNode *
|
|
gsk_texture_scale_node_new (GdkTexture *texture,
|
|
const graphene_rect_t *bounds,
|
|
GskScalingFilter filter)
|
|
{
|
|
GskTextureScaleNode *self;
|
|
GskRenderNode *node;
|
|
|
|
g_return_val_if_fail (GDK_IS_TEXTURE (texture), NULL);
|
|
g_return_val_if_fail (bounds != NULL, NULL);
|
|
|
|
self = gsk_render_node_alloc (GSK_TEXTURE_SCALE_NODE);
|
|
node = (GskRenderNode *) self;
|
|
node->offscreen_for_opacity = FALSE;
|
|
node->fully_opaque = gdk_memory_format_alpha (gdk_texture_get_format (texture)) == GDK_MEMORY_ALPHA_OPAQUE &&
|
|
bounds->size.width == floor (bounds->size.width) &&
|
|
bounds->size.height == floor (bounds->size.height);
|
|
|
|
self->texture = g_object_ref (texture);
|
|
gsk_rect_init_from_rect (&node->bounds, bounds);
|
|
gsk_rect_normalize (&node->bounds);
|
|
self->filter = filter;
|
|
|
|
node->preferred_depth = gdk_texture_get_depth (texture);
|
|
|
|
return node;
|
|
}
|
|
|
|
/* }}} */
|
|
/* {{{ GSK_INSET_SHADOW_NODE */
|
|
|
|
/**
|
|
* GskInsetShadowNode:
|
|
*
|
|
* A render node for an inset shadow.
|
|
*/
|
|
struct _GskInsetShadowNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
GskRoundedRect outline;
|
|
GdkRGBA color;
|
|
float dx;
|
|
float dy;
|
|
float spread;
|
|
float blur_radius;
|
|
};
|
|
|
|
static gboolean
|
|
has_empty_clip (cairo_t *cr)
|
|
{
|
|
double x1, y1, x2, y2;
|
|
|
|
cairo_clip_extents (cr, &x1, &y1, &x2, &y2);
|
|
return x1 >= x2 || y1 >= y2;
|
|
}
|
|
|
|
static void
|
|
draw_shadow (cairo_t *cr,
|
|
GdkColorState *ccs,
|
|
gboolean inset,
|
|
const GskRoundedRect *box,
|
|
const GskRoundedRect *clip_box,
|
|
float radius,
|
|
const GdkRGBA *color,
|
|
GskBlurFlags blur_flags)
|
|
{
|
|
cairo_t *shadow_cr;
|
|
|
|
if (has_empty_clip (cr))
|
|
return;
|
|
|
|
gdk_cairo_set_source_rgba_ccs (cr, ccs, color);
|
|
shadow_cr = gsk_cairo_blur_start_drawing (cr, radius, blur_flags);
|
|
|
|
cairo_set_fill_rule (shadow_cr, CAIRO_FILL_RULE_EVEN_ODD);
|
|
gsk_rounded_rect_path (box, shadow_cr);
|
|
if (inset)
|
|
gdk_cairo_rect (shadow_cr, &clip_box->bounds);
|
|
|
|
cairo_fill (shadow_cr);
|
|
|
|
gsk_cairo_blur_finish_drawing (shadow_cr, ccs, radius, color, blur_flags);
|
|
}
|
|
|
|
typedef struct {
|
|
float radius;
|
|
graphene_size_t corner;
|
|
} CornerMask;
|
|
|
|
typedef enum {
|
|
TOP,
|
|
RIGHT,
|
|
BOTTOM,
|
|
LEFT
|
|
} Side;
|
|
|
|
static guint
|
|
corner_mask_hash (CornerMask *mask)
|
|
{
|
|
return ((guint)mask->radius << 24) ^
|
|
((guint)(mask->corner.width*4)) << 12 ^
|
|
((guint)(mask->corner.height*4)) << 0;
|
|
}
|
|
|
|
static gboolean
|
|
corner_mask_equal (CornerMask *mask1,
|
|
CornerMask *mask2)
|
|
{
|
|
return
|
|
mask1->radius == mask2->radius &&
|
|
mask1->corner.width == mask2->corner.width &&
|
|
mask1->corner.height == mask2->corner.height;
|
|
}
|
|
|
|
static void
|
|
draw_shadow_corner (cairo_t *cr,
|
|
GdkColorState *ccs,
|
|
gboolean inset,
|
|
const GskRoundedRect *box,
|
|
const GskRoundedRect *clip_box,
|
|
float radius,
|
|
const GdkRGBA *color,
|
|
GskCorner corner,
|
|
cairo_rectangle_int_t *drawn_rect)
|
|
{
|
|
float clip_radius;
|
|
int x1, x2, x3, y1, y2, y3, x, y;
|
|
GskRoundedRect corner_box;
|
|
cairo_t *mask_cr;
|
|
cairo_surface_t *mask;
|
|
cairo_pattern_t *pattern;
|
|
cairo_matrix_t matrix;
|
|
float sx, sy;
|
|
static GHashTable *corner_mask_cache = NULL;
|
|
float max_other;
|
|
CornerMask key;
|
|
gboolean overlapped;
|
|
|
|
clip_radius = gsk_cairo_blur_compute_pixels (radius);
|
|
|
|
overlapped = FALSE;
|
|
if (corner == GSK_CORNER_TOP_LEFT || corner == GSK_CORNER_BOTTOM_LEFT)
|
|
{
|
|
x1 = floor (box->bounds.origin.x - clip_radius);
|
|
x2 = ceil (box->bounds.origin.x + box->corner[corner].width + clip_radius);
|
|
x = x1;
|
|
sx = 1;
|
|
max_other = MAX(box->corner[GSK_CORNER_TOP_RIGHT].width, box->corner[GSK_CORNER_BOTTOM_RIGHT].width);
|
|
x3 = floor (box->bounds.origin.x + box->bounds.size.width - max_other - clip_radius);
|
|
if (x2 > x3)
|
|
overlapped = TRUE;
|
|
}
|
|
else
|
|
{
|
|
x1 = floor (box->bounds.origin.x + box->bounds.size.width - box->corner[corner].width - clip_radius);
|
|
x2 = ceil (box->bounds.origin.x + box->bounds.size.width + clip_radius);
|
|
x = x2;
|
|
sx = -1;
|
|
max_other = MAX(box->corner[GSK_CORNER_TOP_LEFT].width, box->corner[GSK_CORNER_BOTTOM_LEFT].width);
|
|
x3 = ceil (box->bounds.origin.x + max_other + clip_radius);
|
|
if (x3 > x1)
|
|
overlapped = TRUE;
|
|
}
|
|
|
|
if (corner == GSK_CORNER_TOP_LEFT || corner == GSK_CORNER_TOP_RIGHT)
|
|
{
|
|
y1 = floor (box->bounds.origin.y - clip_radius);
|
|
y2 = ceil (box->bounds.origin.y + box->corner[corner].height + clip_radius);
|
|
y = y1;
|
|
sy = 1;
|
|
max_other = MAX(box->corner[GSK_CORNER_BOTTOM_LEFT].height, box->corner[GSK_CORNER_BOTTOM_RIGHT].height);
|
|
y3 = floor (box->bounds.origin.y + box->bounds.size.height - max_other - clip_radius);
|
|
if (y2 > y3)
|
|
overlapped = TRUE;
|
|
}
|
|
else
|
|
{
|
|
y1 = floor (box->bounds.origin.y + box->bounds.size.height - box->corner[corner].height - clip_radius);
|
|
y2 = ceil (box->bounds.origin.y + box->bounds.size.height + clip_radius);
|
|
y = y2;
|
|
sy = -1;
|
|
max_other = MAX(box->corner[GSK_CORNER_TOP_LEFT].height, box->corner[GSK_CORNER_TOP_RIGHT].height);
|
|
y3 = ceil (box->bounds.origin.y + max_other + clip_radius);
|
|
if (y3 > y1)
|
|
overlapped = TRUE;
|
|
}
|
|
|
|
drawn_rect->x = x1;
|
|
drawn_rect->y = y1;
|
|
drawn_rect->width = x2 - x1;
|
|
drawn_rect->height = y2 - y1;
|
|
|
|
cairo_rectangle (cr, x1, y1, x2 - x1, y2 - y1);
|
|
cairo_clip (cr);
|
|
|
|
if (inset || overlapped)
|
|
{
|
|
/* Fall back to generic path if inset or if the corner radius
|
|
runs into each other */
|
|
draw_shadow (cr, ccs, inset, box, clip_box, radius, color, GSK_BLUR_X | GSK_BLUR_Y);
|
|
return;
|
|
}
|
|
|
|
if (has_empty_clip (cr))
|
|
return;
|
|
|
|
/* At this point we're drawing a blurred outset corner. The only
|
|
* things that affect the output of the blurred mask in this case
|
|
* is:
|
|
*
|
|
* What corner this is, which defines the orientation (sx,sy)
|
|
* and position (x,y)
|
|
*
|
|
* The blur radius (which also defines the clip_radius)
|
|
*
|
|
* The horizontal and vertical corner radius
|
|
*
|
|
* We apply the first position and orientation when drawing the
|
|
* mask, so we cache rendered masks based on the blur radius and the
|
|
* corner radius.
|
|
*/
|
|
if (corner_mask_cache == NULL)
|
|
corner_mask_cache = g_hash_table_new_full ((GHashFunc)corner_mask_hash,
|
|
(GEqualFunc)corner_mask_equal,
|
|
g_free, (GDestroyNotify)cairo_surface_destroy);
|
|
|
|
key.radius = radius;
|
|
key.corner = box->corner[corner];
|
|
|
|
mask = g_hash_table_lookup (corner_mask_cache, &key);
|
|
if (mask == NULL)
|
|
{
|
|
mask = cairo_surface_create_similar_image (cairo_get_target (cr), CAIRO_FORMAT_A8,
|
|
drawn_rect->width + clip_radius,
|
|
drawn_rect->height + clip_radius);
|
|
mask_cr = cairo_create (mask);
|
|
gsk_rounded_rect_init_from_rect (&corner_box, &GRAPHENE_RECT_INIT (clip_radius, clip_radius, 2*drawn_rect->width, 2*drawn_rect->height), 0);
|
|
corner_box.corner[0] = box->corner[corner];
|
|
gsk_rounded_rect_path (&corner_box, mask_cr);
|
|
cairo_fill (mask_cr);
|
|
gsk_cairo_blur_surface (mask, radius, GSK_BLUR_X | GSK_BLUR_Y);
|
|
cairo_destroy (mask_cr);
|
|
g_hash_table_insert (corner_mask_cache, g_memdup2 (&key, sizeof (key)), mask);
|
|
}
|
|
|
|
gdk_cairo_set_source_rgba_ccs (cr, ccs, color);
|
|
pattern = cairo_pattern_create_for_surface (mask);
|
|
cairo_matrix_init_identity (&matrix);
|
|
cairo_matrix_scale (&matrix, sx, sy);
|
|
cairo_matrix_translate (&matrix, -x, -y);
|
|
cairo_pattern_set_matrix (pattern, &matrix);
|
|
cairo_mask (cr, pattern);
|
|
cairo_pattern_destroy (pattern);
|
|
}
|
|
|
|
static void
|
|
draw_shadow_side (cairo_t *cr,
|
|
GdkColorState *ccs,
|
|
gboolean inset,
|
|
const GskRoundedRect *box,
|
|
const GskRoundedRect *clip_box,
|
|
float radius,
|
|
const GdkRGBA *color,
|
|
Side side,
|
|
cairo_rectangle_int_t *drawn_rect)
|
|
{
|
|
GskBlurFlags blur_flags = GSK_BLUR_REPEAT;
|
|
double clip_radius;
|
|
int x1, x2, y1, y2;
|
|
|
|
clip_radius = gsk_cairo_blur_compute_pixels (radius);
|
|
|
|
if (side == TOP || side == BOTTOM)
|
|
{
|
|
blur_flags |= GSK_BLUR_Y;
|
|
x1 = floor (box->bounds.origin.x - clip_radius);
|
|
x2 = ceil (box->bounds.origin.x + box->bounds.size.width + clip_radius);
|
|
}
|
|
else if (side == LEFT)
|
|
{
|
|
x1 = floor (box->bounds.origin.x -clip_radius);
|
|
x2 = ceil (box->bounds.origin.x + clip_radius);
|
|
}
|
|
else
|
|
{
|
|
x1 = floor (box->bounds.origin.x + box->bounds.size.width -clip_radius);
|
|
x2 = ceil (box->bounds.origin.x + box->bounds.size.width + clip_radius);
|
|
}
|
|
|
|
if (side == LEFT || side == RIGHT)
|
|
{
|
|
blur_flags |= GSK_BLUR_X;
|
|
y1 = floor (box->bounds.origin.y - clip_radius);
|
|
y2 = ceil (box->bounds.origin.y + box->bounds.size.height + clip_radius);
|
|
}
|
|
else if (side == TOP)
|
|
{
|
|
y1 = floor (box->bounds.origin.y -clip_radius);
|
|
y2 = ceil (box->bounds.origin.y + clip_radius);
|
|
}
|
|
else
|
|
{
|
|
y1 = floor (box->bounds.origin.y + box->bounds.size.height -clip_radius);
|
|
y2 = ceil (box->bounds.origin.y + box->bounds.size.height + clip_radius);
|
|
}
|
|
|
|
drawn_rect->x = x1;
|
|
drawn_rect->y = y1;
|
|
drawn_rect->width = x2 - x1;
|
|
drawn_rect->height = y2 - y1;
|
|
|
|
cairo_rectangle (cr, x1, y1, x2 - x1, y2 - y1);
|
|
cairo_clip (cr);
|
|
draw_shadow (cr, ccs, inset, box, clip_box, radius, color, blur_flags);
|
|
}
|
|
|
|
static gboolean
|
|
needs_blur (double radius)
|
|
{
|
|
/* The code doesn't actually do any blurring for radius 1, as it
|
|
* ends up with box filter size 1 */
|
|
if (radius <= 1.0)
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gsk_inset_shadow_node_draw (GskRenderNode *node,
|
|
cairo_t *cr,
|
|
GdkColorState *ccs)
|
|
{
|
|
GskInsetShadowNode *self = (GskInsetShadowNode *) node;
|
|
GskRoundedRect box, clip_box;
|
|
int clip_radius;
|
|
graphene_rect_t clip_rect;
|
|
double blur_radius;
|
|
|
|
/* We don't need to draw invisible shadows */
|
|
if (gdk_rgba_is_clear (&self->color))
|
|
return;
|
|
|
|
_graphene_rect_init_from_clip_extents (&clip_rect, cr);
|
|
if (!gsk_rounded_rect_intersects_rect (&self->outline, &clip_rect))
|
|
return;
|
|
|
|
blur_radius = self->blur_radius / 2;
|
|
|
|
clip_radius = gsk_cairo_blur_compute_pixels (blur_radius);
|
|
|
|
cairo_save (cr);
|
|
|
|
gsk_rounded_rect_path (&self->outline, cr);
|
|
cairo_clip (cr);
|
|
|
|
gsk_rounded_rect_init_copy (&box, &self->outline);
|
|
gsk_rounded_rect_offset (&box, self->dx, self->dy);
|
|
gsk_rounded_rect_shrink (&box, self->spread, self->spread, self->spread, self->spread);
|
|
|
|
gsk_rounded_rect_init_copy (&clip_box, &self->outline);
|
|
gsk_rounded_rect_shrink (&clip_box, -clip_radius, -clip_radius, -clip_radius, -clip_radius);
|
|
|
|
if (!needs_blur (blur_radius))
|
|
draw_shadow (cr, ccs, TRUE, &box, &clip_box, blur_radius, &self->color, GSK_BLUR_NONE);
|
|
else
|
|
{
|
|
cairo_region_t *remaining;
|
|
cairo_rectangle_int_t r;
|
|
int i;
|
|
|
|
/* For the blurred case we divide the rendering into 9 parts,
|
|
* 4 of the corners, 4 for the horizonat/vertical lines and
|
|
* one for the interior. We make the non-interior parts
|
|
* large enough to fit the full radius of the blur, so that
|
|
* the interior part can be drawn solidly.
|
|
*/
|
|
|
|
/* In the inset case we want to paint the whole clip-box.
|
|
* We could remove the part of "box" where the blur doesn't
|
|
* reach, but computing that is a bit tricky since the
|
|
* rounded corners are on the "inside" of it. */
|
|
gsk_rect_to_cairo_grow (&clip_box.bounds, &r);
|
|
remaining = cairo_region_create_rectangle (&r);
|
|
|
|
/* First do the corners of box */
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
cairo_save (cr);
|
|
/* Always clip with remaining to ensure we never draw any area twice */
|
|
gdk_cairo_region (cr, remaining);
|
|
cairo_clip (cr);
|
|
draw_shadow_corner (cr, ccs, TRUE, &box, &clip_box, blur_radius, &self->color, i, &r);
|
|
cairo_restore (cr);
|
|
|
|
/* We drew the region, remove it from remaining */
|
|
cairo_region_subtract_rectangle (remaining, &r);
|
|
}
|
|
|
|
/* Then the sides */
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
cairo_save (cr);
|
|
/* Always clip with remaining to ensure we never draw any area twice */
|
|
gdk_cairo_region (cr, remaining);
|
|
cairo_clip (cr);
|
|
draw_shadow_side (cr, ccs, TRUE, &box, &clip_box, blur_radius, &self->color, i, &r);
|
|
cairo_restore (cr);
|
|
|
|
/* We drew the region, remove it from remaining */
|
|
cairo_region_subtract_rectangle (remaining, &r);
|
|
}
|
|
|
|
/* Then the rest, which needs no blurring */
|
|
|
|
cairo_save (cr);
|
|
gdk_cairo_region (cr, remaining);
|
|
cairo_clip (cr);
|
|
draw_shadow (cr, ccs, TRUE, &box, &clip_box, blur_radius, &self->color, GSK_BLUR_NONE);
|
|
cairo_restore (cr);
|
|
|
|
cairo_region_destroy (remaining);
|
|
}
|
|
|
|
cairo_restore (cr);
|
|
}
|
|
|
|
static void
|
|
gsk_inset_shadow_node_diff (GskRenderNode *node1,
|
|
GskRenderNode *node2,
|
|
GskDiffData *data)
|
|
{
|
|
GskInsetShadowNode *self1 = (GskInsetShadowNode *) node1;
|
|
GskInsetShadowNode *self2 = (GskInsetShadowNode *) node2;
|
|
|
|
if (gsk_rounded_rect_equal (&self1->outline, &self2->outline) &&
|
|
gdk_rgba_equal (&self1->color, &self2->color) &&
|
|
self1->dx == self2->dx &&
|
|
self1->dy == self2->dy &&
|
|
self1->spread == self2->spread &&
|
|
self1->blur_radius == self2->blur_radius)
|
|
return;
|
|
|
|
gsk_render_node_diff_impossible (node1, node2, data);
|
|
}
|
|
|
|
static void
|
|
gsk_inset_shadow_node_class_init (gpointer g_class,
|
|
gpointer class_data)
|
|
{
|
|
GskRenderNodeClass *node_class = g_class;
|
|
|
|
node_class->node_type = GSK_INSET_SHADOW_NODE;
|
|
|
|
node_class->draw = gsk_inset_shadow_node_draw;
|
|
node_class->diff = gsk_inset_shadow_node_diff;
|
|
}
|
|
|
|
/**
|
|
* gsk_inset_shadow_node_new:
|
|
* @outline: outline of the region containing the shadow
|
|
* @color: color of the shadow
|
|
* @dx: horizontal offset of shadow
|
|
* @dy: vertical offset of shadow
|
|
* @spread: how far the shadow spreads towards the inside
|
|
* @blur_radius: how much blur to apply to the shadow
|
|
*
|
|
* Creates a `GskRenderNode` that will render an inset shadow
|
|
* into the box given by @outline.
|
|
*
|
|
* Returns: (transfer full) (type GskInsetShadowNode): A new `GskRenderNode`
|
|
*/
|
|
GskRenderNode *
|
|
gsk_inset_shadow_node_new (const GskRoundedRect *outline,
|
|
const GdkRGBA *color,
|
|
float dx,
|
|
float dy,
|
|
float spread,
|
|
float blur_radius)
|
|
{
|
|
GskInsetShadowNode *self;
|
|
GskRenderNode *node;
|
|
|
|
g_return_val_if_fail (outline != NULL, NULL);
|
|
g_return_val_if_fail (color != NULL, NULL);
|
|
g_return_val_if_fail (blur_radius >= 0, NULL);
|
|
|
|
self = gsk_render_node_alloc (GSK_INSET_SHADOW_NODE);
|
|
node = (GskRenderNode *) self;
|
|
node->offscreen_for_opacity = FALSE;
|
|
node->preferred_depth = my_color_get_depth (color);
|
|
|
|
gsk_rounded_rect_init_copy (&self->outline, outline);
|
|
self->color = *color;
|
|
self->dx = dx;
|
|
self->dy = dy;
|
|
self->spread = spread;
|
|
self->blur_radius = blur_radius;
|
|
|
|
gsk_rect_init_from_rect (&node->bounds, &self->outline.bounds);
|
|
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* gsk_inset_shadow_node_get_outline:
|
|
* @node: (type GskInsetShadowNode): a `GskRenderNode` for an inset shadow
|
|
*
|
|
* Retrieves the outline rectangle of the inset shadow.
|
|
*
|
|
* Returns: (transfer none): a rounded rectangle
|
|
*/
|
|
const GskRoundedRect *
|
|
gsk_inset_shadow_node_get_outline (const GskRenderNode *node)
|
|
{
|
|
const GskInsetShadowNode *self = (const GskInsetShadowNode *) node;
|
|
|
|
return &self->outline;
|
|
}
|
|
|
|
/**
|
|
* gsk_inset_shadow_node_get_color:
|
|
* @node: (type GskInsetShadowNode): a `GskRenderNode` for an inset shadow
|
|
*
|
|
* Retrieves the color of the inset shadow.
|
|
*
|
|
* Returns: (transfer none): the color of the shadow
|
|
*/
|
|
const GdkRGBA *
|
|
gsk_inset_shadow_node_get_color (const GskRenderNode *node)
|
|
{
|
|
const GskInsetShadowNode *self = (const GskInsetShadowNode *) node;
|
|
|
|
return &self->color;
|
|
}
|
|
|
|
/**
|
|
* gsk_inset_shadow_node_get_dx:
|
|
* @node: (type GskInsetShadowNode): a `GskRenderNode` for an inset shadow
|
|
*
|
|
* Retrieves the horizontal offset of the inset shadow.
|
|
*
|
|
* Returns: an offset, in pixels
|
|
*/
|
|
float
|
|
gsk_inset_shadow_node_get_dx (const GskRenderNode *node)
|
|
{
|
|
const GskInsetShadowNode *self = (const GskInsetShadowNode *) node;
|
|
|
|
return self->dx;
|
|
}
|
|
|
|
/**
|
|
* gsk_inset_shadow_node_get_dy:
|
|
* @node: (type GskInsetShadowNode): a `GskRenderNode` for an inset shadow
|
|
*
|
|
* Retrieves the vertical offset of the inset shadow.
|
|
*
|
|
* Returns: an offset, in pixels
|
|
*/
|
|
float
|
|
gsk_inset_shadow_node_get_dy (const GskRenderNode *node)
|
|
{
|
|
const GskInsetShadowNode *self = (const GskInsetShadowNode *) node;
|
|
|
|
return self->dy;
|
|
}
|
|
|
|
/**
|
|
* gsk_inset_shadow_node_get_spread:
|
|
* @node: (type GskInsetShadowNode): a `GskRenderNode` for an inset shadow
|
|
*
|
|
* Retrieves how much the shadow spreads inwards.
|
|
*
|
|
* Returns: the size of the shadow, in pixels
|
|
*/
|
|
float
|
|
gsk_inset_shadow_node_get_spread (const GskRenderNode *node)
|
|
{
|
|
const GskInsetShadowNode *self = (const GskInsetShadowNode *) node;
|
|
|
|
return self->spread;
|
|
}
|
|
|
|
/**
|
|
* gsk_inset_shadow_node_get_blur_radius:
|
|
* @node: (type GskInsetShadowNode): a `GskRenderNode` for an inset shadow
|
|
*
|
|
* Retrieves the blur radius to apply to the shadow.
|
|
*
|
|
* Returns: the blur radius, in pixels
|
|
*/
|
|
float
|
|
gsk_inset_shadow_node_get_blur_radius (const GskRenderNode *node)
|
|
{
|
|
const GskInsetShadowNode *self = (const GskInsetShadowNode *) node;
|
|
|
|
return self->blur_radius;
|
|
}
|
|
|
|
/* }}} */
|
|
/* {{{ GSK_OUTSET_SHADOW_NODE */
|
|
|
|
/**
|
|
* GskOutsetShadowNode:
|
|
*
|
|
* A render node for an outset shadow.
|
|
*/
|
|
struct _GskOutsetShadowNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
GskRoundedRect outline;
|
|
GdkRGBA color;
|
|
float dx;
|
|
float dy;
|
|
float spread;
|
|
float blur_radius;
|
|
};
|
|
|
|
static void
|
|
gsk_outset_shadow_get_extents (GskOutsetShadowNode *self,
|
|
float *top,
|
|
float *right,
|
|
float *bottom,
|
|
float *left)
|
|
{
|
|
float clip_radius;
|
|
|
|
clip_radius = gsk_cairo_blur_compute_pixels (ceil (self->blur_radius / 2.0));
|
|
*top = MAX (0, ceil (clip_radius + self->spread - self->dy));
|
|
*right = MAX (0, ceil (clip_radius + self->spread + self->dx));
|
|
*bottom = MAX (0, ceil (clip_radius + self->spread + self->dy));
|
|
*left = MAX (0, ceil (clip_radius + self->spread - self->dx));
|
|
}
|
|
|
|
static void
|
|
gsk_outset_shadow_node_draw (GskRenderNode *node,
|
|
cairo_t *cr,
|
|
GdkColorState *ccs)
|
|
{
|
|
GskOutsetShadowNode *self = (GskOutsetShadowNode *) node;
|
|
GskRoundedRect box, clip_box;
|
|
int clip_radius;
|
|
graphene_rect_t clip_rect;
|
|
float top, right, bottom, left;
|
|
double blur_radius;
|
|
|
|
/* We don't need to draw invisible shadows */
|
|
if (gdk_rgba_is_clear (&self->color))
|
|
return;
|
|
|
|
_graphene_rect_init_from_clip_extents (&clip_rect, cr);
|
|
if (!gsk_rounded_rect_intersects_rect (&self->outline, &clip_rect))
|
|
return;
|
|
|
|
blur_radius = self->blur_radius / 2;
|
|
|
|
clip_radius = gsk_cairo_blur_compute_pixels (blur_radius);
|
|
|
|
cairo_save (cr);
|
|
|
|
gsk_rounded_rect_init_copy (&clip_box, &self->outline);
|
|
gsk_outset_shadow_get_extents (self, &top, &right, &bottom, &left);
|
|
gsk_rounded_rect_shrink (&clip_box, -top, -right, -bottom, -left);
|
|
|
|
cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);
|
|
gsk_rounded_rect_path (&self->outline, cr);
|
|
gdk_cairo_rect (cr, &clip_box.bounds);
|
|
|
|
cairo_clip (cr);
|
|
|
|
gsk_rounded_rect_init_copy (&box, &self->outline);
|
|
gsk_rounded_rect_offset (&box, self->dx, self->dy);
|
|
gsk_rounded_rect_shrink (&box, -self->spread, -self->spread, -self->spread, -self->spread);
|
|
|
|
if (!needs_blur (blur_radius))
|
|
draw_shadow (cr, ccs, FALSE, &box, &clip_box, blur_radius, &self->color, GSK_BLUR_NONE);
|
|
else
|
|
{
|
|
int i;
|
|
cairo_region_t *remaining;
|
|
cairo_rectangle_int_t r;
|
|
|
|
/* For the blurred case we divide the rendering into 9 parts,
|
|
* 4 of the corners, 4 for the horizonat/vertical lines and
|
|
* one for the interior. We make the non-interior parts
|
|
* large enough to fit the full radius of the blur, so that
|
|
* the interior part can be drawn solidly.
|
|
*/
|
|
|
|
/* In the outset case we want to paint the entire box, plus as far
|
|
* as the radius reaches from it */
|
|
r.x = floor (box.bounds.origin.x - clip_radius);
|
|
r.y = floor (box.bounds.origin.y - clip_radius);
|
|
r.width = ceil (box.bounds.origin.x + box.bounds.size.width + clip_radius) - r.x;
|
|
r.height = ceil (box.bounds.origin.y + box.bounds.size.height + clip_radius) - r.y;
|
|
|
|
remaining = cairo_region_create_rectangle (&r);
|
|
|
|
/* First do the corners of box */
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
cairo_save (cr);
|
|
/* Always clip with remaining to ensure we never draw any area twice */
|
|
gdk_cairo_region (cr, remaining);
|
|
cairo_clip (cr);
|
|
draw_shadow_corner (cr, ccs, FALSE, &box, &clip_box, blur_radius, &self->color, i, &r);
|
|
cairo_restore (cr);
|
|
|
|
/* We drew the region, remove it from remaining */
|
|
cairo_region_subtract_rectangle (remaining, &r);
|
|
}
|
|
|
|
/* Then the sides */
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
cairo_save (cr);
|
|
/* Always clip with remaining to ensure we never draw any area twice */
|
|
gdk_cairo_region (cr, remaining);
|
|
cairo_clip (cr);
|
|
draw_shadow_side (cr, ccs, FALSE, &box, &clip_box, blur_radius, &self->color, i, &r);
|
|
cairo_restore (cr);
|
|
|
|
/* We drew the region, remove it from remaining */
|
|
cairo_region_subtract_rectangle (remaining, &r);
|
|
}
|
|
|
|
/* Then the rest, which needs no blurring */
|
|
|
|
cairo_save (cr);
|
|
gdk_cairo_region (cr, remaining);
|
|
cairo_clip (cr);
|
|
draw_shadow (cr, ccs, FALSE, &box, &clip_box, blur_radius, &self->color, GSK_BLUR_NONE);
|
|
cairo_restore (cr);
|
|
|
|
cairo_region_destroy (remaining);
|
|
}
|
|
|
|
cairo_restore (cr);
|
|
}
|
|
|
|
static void
|
|
gsk_outset_shadow_node_diff (GskRenderNode *node1,
|
|
GskRenderNode *node2,
|
|
GskDiffData *data)
|
|
{
|
|
GskOutsetShadowNode *self1 = (GskOutsetShadowNode *) node1;
|
|
GskOutsetShadowNode *self2 = (GskOutsetShadowNode *) node2;
|
|
|
|
if (gsk_rounded_rect_equal (&self1->outline, &self2->outline) &&
|
|
gdk_rgba_equal (&self1->color, &self2->color) &&
|
|
self1->dx == self2->dx &&
|
|
self1->dy == self2->dy &&
|
|
self1->spread == self2->spread &&
|
|
self1->blur_radius == self2->blur_radius)
|
|
return;
|
|
|
|
gsk_render_node_diff_impossible (node1, node2, data);
|
|
}
|
|
|
|
static void
|
|
gsk_outset_shadow_node_class_init (gpointer g_class,
|
|
gpointer class_data)
|
|
{
|
|
GskRenderNodeClass *node_class = g_class;
|
|
|
|
node_class->node_type = GSK_OUTSET_SHADOW_NODE;
|
|
|
|
node_class->draw = gsk_outset_shadow_node_draw;
|
|
node_class->diff = gsk_outset_shadow_node_diff;
|
|
}
|
|
|
|
/**
|
|
* gsk_outset_shadow_node_new:
|
|
* @outline: outline of the region surrounded by shadow
|
|
* @color: color of the shadow
|
|
* @dx: horizontal offset of shadow
|
|
* @dy: vertical offset of shadow
|
|
* @spread: how far the shadow spreads towards the inside
|
|
* @blur_radius: how much blur to apply to the shadow
|
|
*
|
|
* Creates a `GskRenderNode` that will render an outset shadow
|
|
* around the box given by @outline.
|
|
*
|
|
* Returns: (transfer full) (type GskOutsetShadowNode): A new `GskRenderNode`
|
|
*/
|
|
GskRenderNode *
|
|
gsk_outset_shadow_node_new (const GskRoundedRect *outline,
|
|
const GdkRGBA *color,
|
|
float dx,
|
|
float dy,
|
|
float spread,
|
|
float blur_radius)
|
|
{
|
|
GskOutsetShadowNode *self;
|
|
GskRenderNode *node;
|
|
float top, right, bottom, left;
|
|
|
|
g_return_val_if_fail (outline != NULL, NULL);
|
|
g_return_val_if_fail (color != NULL, NULL);
|
|
g_return_val_if_fail (blur_radius >= 0, NULL);
|
|
|
|
self = gsk_render_node_alloc (GSK_OUTSET_SHADOW_NODE);
|
|
node = (GskRenderNode *) self;
|
|
node->offscreen_for_opacity = FALSE;
|
|
node->preferred_depth = my_color_get_depth (color);
|
|
|
|
gsk_rounded_rect_init_copy (&self->outline, outline);
|
|
self->color = *color;
|
|
self->dx = dx;
|
|
self->dy = dy;
|
|
self->spread = spread;
|
|
self->blur_radius = blur_radius;
|
|
|
|
gsk_outset_shadow_get_extents (self, &top, &right, &bottom, &left);
|
|
|
|
gsk_rect_init_from_rect (&node->bounds, &self->outline.bounds);
|
|
node->bounds.origin.x -= left;
|
|
node->bounds.origin.y -= top;
|
|
node->bounds.size.width += left + right;
|
|
node->bounds.size.height += top + bottom;
|
|
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* gsk_outset_shadow_node_get_outline:
|
|
* @node: (type GskOutsetShadowNode): a `GskRenderNode` for an outset shadow
|
|
*
|
|
* Retrieves the outline rectangle of the outset shadow.
|
|
*
|
|
* Returns: (transfer none): a rounded rectangle
|
|
*/
|
|
const GskRoundedRect *
|
|
gsk_outset_shadow_node_get_outline (const GskRenderNode *node)
|
|
{
|
|
const GskOutsetShadowNode *self = (const GskOutsetShadowNode *) node;
|
|
|
|
return &self->outline;
|
|
}
|
|
|
|
/**
|
|
* gsk_outset_shadow_node_get_color:
|
|
* @node: (type GskOutsetShadowNode): a `GskRenderNode` for an outset shadow
|
|
*
|
|
* Retrieves the color of the outset shadow.
|
|
*
|
|
* Returns: (transfer none): a color
|
|
*/
|
|
const GdkRGBA *
|
|
gsk_outset_shadow_node_get_color (const GskRenderNode *node)
|
|
{
|
|
const GskOutsetShadowNode *self = (const GskOutsetShadowNode *) node;
|
|
|
|
return &self->color;
|
|
}
|
|
|
|
/**
|
|
* gsk_outset_shadow_node_get_dx:
|
|
* @node: (type GskOutsetShadowNode): a `GskRenderNode` for an outset shadow
|
|
*
|
|
* Retrieves the horizontal offset of the outset shadow.
|
|
*
|
|
* Returns: an offset, in pixels
|
|
*/
|
|
float
|
|
gsk_outset_shadow_node_get_dx (const GskRenderNode *node)
|
|
{
|
|
const GskOutsetShadowNode *self = (const GskOutsetShadowNode *) node;
|
|
|
|
return self->dx;
|
|
}
|
|
|
|
/**
|
|
* gsk_outset_shadow_node_get_dy:
|
|
* @node: (type GskOutsetShadowNode): a `GskRenderNode` for an outset shadow
|
|
*
|
|
* Retrieves the vertical offset of the outset shadow.
|
|
*
|
|
* Returns: an offset, in pixels
|
|
*/
|
|
float
|
|
gsk_outset_shadow_node_get_dy (const GskRenderNode *node)
|
|
{
|
|
const GskOutsetShadowNode *self = (const GskOutsetShadowNode *) node;
|
|
|
|
return self->dy;
|
|
}
|
|
|
|
/**
|
|
* gsk_outset_shadow_node_get_spread:
|
|
* @node: (type GskOutsetShadowNode): a `GskRenderNode` for an outset shadow
|
|
*
|
|
* Retrieves how much the shadow spreads outwards.
|
|
*
|
|
* Returns: the size of the shadow, in pixels
|
|
*/
|
|
float
|
|
gsk_outset_shadow_node_get_spread (const GskRenderNode *node)
|
|
{
|
|
const GskOutsetShadowNode *self = (const GskOutsetShadowNode *) node;
|
|
|
|
return self->spread;
|
|
}
|
|
|
|
/**
|
|
* gsk_outset_shadow_node_get_blur_radius:
|
|
* @node: (type GskOutsetShadowNode): a `GskRenderNode` for an outset shadow
|
|
*
|
|
* Retrieves the blur radius of the shadow.
|
|
*
|
|
* Returns: the blur radius, in pixels
|
|
*/
|
|
float
|
|
gsk_outset_shadow_node_get_blur_radius (const GskRenderNode *node)
|
|
{
|
|
const GskOutsetShadowNode *self = (const GskOutsetShadowNode *) node;
|
|
|
|
return self->blur_radius;
|
|
}
|
|
|
|
/* }}} */
|
|
/* {{{ GSK_CAIRO_NODE */
|
|
|
|
/**
|
|
* GskCairoNode:
|
|
*
|
|
* A render node for a Cairo surface.
|
|
*/
|
|
struct _GskCairoNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
cairo_surface_t *surface;
|
|
};
|
|
|
|
static void
|
|
gsk_cairo_node_finalize (GskRenderNode *node)
|
|
{
|
|
GskCairoNode *self = (GskCairoNode *) node;
|
|
GskRenderNodeClass *parent_class = g_type_class_peek (g_type_parent (GSK_TYPE_CAIRO_NODE));
|
|
|
|
if (self->surface)
|
|
cairo_surface_destroy (self->surface);
|
|
|
|
parent_class->finalize (node);
|
|
}
|
|
|
|
static void
|
|
gsk_cairo_node_draw (GskRenderNode *node,
|
|
cairo_t *cr,
|
|
GdkColorState *ccs)
|
|
{
|
|
GskCairoNode *self = (GskCairoNode *) node;
|
|
|
|
if (self->surface == NULL)
|
|
return;
|
|
|
|
if (gdk_color_state_equal (ccs, GDK_COLOR_STATE_SRGB))
|
|
{
|
|
cairo_set_source_surface (cr, self->surface, 0, 0);
|
|
cairo_paint (cr);
|
|
}
|
|
else
|
|
{
|
|
cairo_save (cr);
|
|
gdk_cairo_rect (cr, &node->bounds);
|
|
cairo_clip (cr);
|
|
cairo_push_group (cr);
|
|
|
|
cairo_set_source_surface (cr, self->surface, 0, 0);
|
|
cairo_paint (cr);
|
|
gdk_cairo_surface_convert_color_state (cairo_get_group_target (cr),
|
|
GDK_COLOR_STATE_SRGB,
|
|
ccs);
|
|
cairo_pop_group_to_source (cr);
|
|
cairo_paint (cr);
|
|
cairo_restore (cr);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gsk_cairo_node_class_init (gpointer g_class,
|
|
gpointer class_data)
|
|
{
|
|
GskRenderNodeClass *node_class = g_class;
|
|
|
|
node_class->node_type = GSK_CAIRO_NODE;
|
|
|
|
node_class->finalize = gsk_cairo_node_finalize;
|
|
node_class->draw = gsk_cairo_node_draw;
|
|
}
|
|
|
|
/**
|
|
* gsk_cairo_node_get_surface:
|
|
* @node: (type GskCairoNode): a `GskRenderNode` for a Cairo surface
|
|
*
|
|
* Retrieves the Cairo surface used by the render node.
|
|
*
|
|
* Returns: (transfer none): a Cairo surface
|
|
*/
|
|
cairo_surface_t *
|
|
gsk_cairo_node_get_surface (GskRenderNode *node)
|
|
{
|
|
GskCairoNode *self = (GskCairoNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_CAIRO_NODE), NULL);
|
|
|
|
return self->surface;
|
|
}
|
|
|
|
/**
|
|
* gsk_cairo_node_new:
|
|
* @bounds: the rectangle to render to
|
|
*
|
|
* Creates a `GskRenderNode` that will render a cairo surface
|
|
* into the area given by @bounds.
|
|
*
|
|
* You can draw to the cairo surface using [method@Gsk.CairoNode.get_draw_context].
|
|
*
|
|
* Returns: (transfer full) (type GskCairoNode): A new `GskRenderNode`
|
|
*/
|
|
GskRenderNode *
|
|
gsk_cairo_node_new (const graphene_rect_t *bounds)
|
|
{
|
|
GskCairoNode *self;
|
|
GskRenderNode *node;
|
|
|
|
g_return_val_if_fail (bounds != NULL, NULL);
|
|
|
|
self = gsk_render_node_alloc (GSK_CAIRO_NODE);
|
|
node = (GskRenderNode *) self;
|
|
node->offscreen_for_opacity = FALSE;
|
|
node->preferred_depth = gdk_color_state_get_depth (GDK_COLOR_STATE_SRGB);
|
|
|
|
gsk_rect_init_from_rect (&node->bounds, bounds);
|
|
gsk_rect_normalize (&node->bounds);
|
|
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* gsk_cairo_node_get_draw_context:
|
|
* @node: (type GskCairoNode): a `GskRenderNode` for a Cairo surface
|
|
*
|
|
* Creates a Cairo context for drawing using the surface associated
|
|
* to the render node.
|
|
*
|
|
* If no surface exists yet, a surface will be created optimized for
|
|
* rendering to @renderer.
|
|
*
|
|
* Returns: (transfer full): a Cairo context used for drawing; use
|
|
* cairo_destroy() when done drawing
|
|
*/
|
|
cairo_t *
|
|
gsk_cairo_node_get_draw_context (GskRenderNode *node)
|
|
{
|
|
GskCairoNode *self = (GskCairoNode *) node;
|
|
int width, height;
|
|
cairo_t *res;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_CAIRO_NODE), NULL);
|
|
|
|
width = ceilf (node->bounds.size.width);
|
|
height = ceilf (node->bounds.size.height);
|
|
|
|
if (width <= 0 || height <= 0)
|
|
{
|
|
cairo_surface_t *surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 0, 0);
|
|
res = cairo_create (surface);
|
|
cairo_surface_destroy (surface);
|
|
}
|
|
else if (self->surface == NULL)
|
|
{
|
|
self->surface = cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA,
|
|
&(cairo_rectangle_t) {
|
|
node->bounds.origin.x,
|
|
node->bounds.origin.y,
|
|
node->bounds.size.width,
|
|
node->bounds.size.height
|
|
});
|
|
res = cairo_create (self->surface);
|
|
}
|
|
else
|
|
{
|
|
res = cairo_create (self->surface);
|
|
}
|
|
|
|
gdk_cairo_rect (res, &node->bounds);
|
|
cairo_clip (res);
|
|
|
|
return res;
|
|
}
|
|
|
|
/* }}} */
|
|
/* {{{ GSK_CONTAINER_NODE */
|
|
|
|
/**
|
|
* GskContainerNode:
|
|
*
|
|
* A render node that can contain other render nodes.
|
|
*/
|
|
struct _GskContainerNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
gboolean disjoint;
|
|
graphene_rect_t opaque; /* Can be 0 0 0 0 to mean no opacity */
|
|
guint n_children;
|
|
GskRenderNode **children;
|
|
};
|
|
|
|
static void
|
|
gsk_container_node_finalize (GskRenderNode *node)
|
|
{
|
|
GskContainerNode *container = (GskContainerNode *) node;
|
|
GskRenderNodeClass *parent_class = g_type_class_peek (g_type_parent (GSK_TYPE_CONTAINER_NODE));
|
|
|
|
for (guint i = 0; i < container->n_children; i++)
|
|
gsk_render_node_unref (container->children[i]);
|
|
|
|
g_free (container->children);
|
|
|
|
parent_class->finalize (node);
|
|
}
|
|
|
|
static void
|
|
gsk_container_node_draw (GskRenderNode *node,
|
|
cairo_t *cr,
|
|
GdkColorState *ccs)
|
|
{
|
|
GskContainerNode *container = (GskContainerNode *) node;
|
|
guint i;
|
|
|
|
for (i = 0; i < container->n_children; i++)
|
|
{
|
|
gsk_render_node_draw_ccs (container->children[i], cr, ccs);
|
|
}
|
|
}
|
|
|
|
static int
|
|
gsk_container_node_compare_func (gconstpointer elem1, gconstpointer elem2, gpointer data)
|
|
{
|
|
return gsk_render_node_can_diff ((const GskRenderNode *) elem1, (const GskRenderNode *) elem2) ? 0 : 1;
|
|
}
|
|
|
|
static GskDiffResult
|
|
gsk_container_node_keep_func (gconstpointer elem1, gconstpointer elem2, gpointer user_data)
|
|
{
|
|
GskDiffData *data = user_data;
|
|
gsk_render_node_diff ((GskRenderNode *) elem1, (GskRenderNode *) elem2, data);
|
|
if (cairo_region_num_rectangles (data->region) > MAX_RECTS_IN_DIFF)
|
|
return GSK_DIFF_ABORTED;
|
|
|
|
return GSK_DIFF_OK;
|
|
}
|
|
|
|
static GskDiffResult
|
|
gsk_container_node_change_func (gconstpointer elem, gsize idx, gpointer user_data)
|
|
{
|
|
const GskRenderNode *node = elem;
|
|
GskDiffData *data = user_data;
|
|
cairo_rectangle_int_t rect;
|
|
|
|
gsk_rect_to_cairo_grow (&node->bounds, &rect);
|
|
cairo_region_union_rectangle (data->region, &rect);
|
|
if (cairo_region_num_rectangles (data->region) > MAX_RECTS_IN_DIFF)
|
|
return GSK_DIFF_ABORTED;
|
|
|
|
return GSK_DIFF_OK;
|
|
}
|
|
|
|
static GskDiffSettings *
|
|
gsk_container_node_get_diff_settings (void)
|
|
{
|
|
static GskDiffSettings *settings = NULL;
|
|
|
|
if (G_LIKELY (settings))
|
|
return settings;
|
|
|
|
settings = gsk_diff_settings_new (gsk_container_node_compare_func,
|
|
gsk_container_node_keep_func,
|
|
gsk_container_node_change_func,
|
|
gsk_container_node_change_func);
|
|
gsk_diff_settings_set_allow_abort (settings, TRUE);
|
|
|
|
return settings;
|
|
}
|
|
|
|
static gboolean
|
|
gsk_render_node_diff_multiple (GskRenderNode **nodes1,
|
|
gsize n_nodes1,
|
|
GskRenderNode **nodes2,
|
|
gsize n_nodes2,
|
|
GskDiffData *data)
|
|
{
|
|
return gsk_diff ((gconstpointer *) nodes1, n_nodes1,
|
|
(gconstpointer *) nodes2, n_nodes2,
|
|
gsk_container_node_get_diff_settings (),
|
|
data) == GSK_DIFF_OK;
|
|
}
|
|
|
|
void
|
|
gsk_container_node_diff_with (GskRenderNode *container,
|
|
GskRenderNode *other,
|
|
GskDiffData *data)
|
|
{
|
|
GskContainerNode *self = (GskContainerNode *) container;
|
|
|
|
if (gsk_render_node_diff_multiple (self->children,
|
|
self->n_children,
|
|
&other,
|
|
1,
|
|
data))
|
|
return;
|
|
|
|
gsk_render_node_diff_impossible (container, other, data);
|
|
}
|
|
|
|
static void
|
|
gsk_container_node_diff (GskRenderNode *node1,
|
|
GskRenderNode *node2,
|
|
GskDiffData *data)
|
|
{
|
|
GskContainerNode *self1 = (GskContainerNode *) node1;
|
|
GskContainerNode *self2 = (GskContainerNode *) node2;
|
|
|
|
if (gsk_render_node_diff_multiple (self1->children,
|
|
self1->n_children,
|
|
self2->children,
|
|
self2->n_children,
|
|
data))
|
|
return;
|
|
|
|
gsk_render_node_diff_impossible (node1, node2, data);
|
|
}
|
|
|
|
static gboolean
|
|
gsk_container_node_get_opaque_rect (GskRenderNode *node,
|
|
graphene_rect_t *opaque)
|
|
{
|
|
GskContainerNode *self = (GskContainerNode *) node;
|
|
|
|
if (self->opaque.size.width <= 0 && self->opaque.size.height <= 0)
|
|
return FALSE;
|
|
|
|
*opaque = self->opaque;
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gsk_container_node_class_init (gpointer g_class,
|
|
gpointer class_data)
|
|
{
|
|
GskRenderNodeClass *node_class = g_class;
|
|
|
|
node_class->node_type = GSK_CONTAINER_NODE;
|
|
|
|
node_class->finalize = gsk_container_node_finalize;
|
|
node_class->draw = gsk_container_node_draw;
|
|
node_class->diff = gsk_container_node_diff;
|
|
node_class->get_opaque_rect = gsk_container_node_get_opaque_rect;
|
|
}
|
|
|
|
/**
|
|
* gsk_container_node_new:
|
|
* @children: (array length=n_children) (transfer none): The children of the node
|
|
* @n_children: Number of children in the @children array
|
|
*
|
|
* Creates a new `GskRenderNode` instance for holding the given @children.
|
|
*
|
|
* The new node will acquire a reference to each of the children.
|
|
*
|
|
* Returns: (transfer full) (type GskContainerNode): the new `GskRenderNode`
|
|
*/
|
|
GskRenderNode *
|
|
gsk_container_node_new (GskRenderNode **children,
|
|
guint n_children)
|
|
{
|
|
GskContainerNode *self;
|
|
GskRenderNode *node;
|
|
|
|
self = gsk_render_node_alloc (GSK_CONTAINER_NODE);
|
|
node = (GskRenderNode *) self;
|
|
|
|
self->disjoint = TRUE;
|
|
self->n_children = n_children;
|
|
|
|
if (n_children == 0)
|
|
{
|
|
gsk_rect_init_from_rect (&node->bounds, graphene_rect_zero ());
|
|
node->preferred_depth = GDK_MEMORY_NONE;
|
|
}
|
|
else
|
|
{
|
|
graphene_rect_t child_opaque;
|
|
gboolean have_opaque;
|
|
|
|
self->children = g_malloc_n (n_children, sizeof (GskRenderNode *));
|
|
|
|
self->children[0] = gsk_render_node_ref (children[0]);
|
|
node->offscreen_for_opacity = children[0]->offscreen_for_opacity;
|
|
node->preferred_depth = children[0]->preferred_depth;
|
|
gsk_rect_init_from_rect (&node->bounds, &(children[0]->bounds));
|
|
have_opaque = gsk_render_node_get_opaque_rect (self->children[0], &self->opaque);
|
|
|
|
for (guint i = 1; i < n_children; i++)
|
|
{
|
|
self->children[i] = gsk_render_node_ref (children[i]);
|
|
self->disjoint = self->disjoint && !gsk_rect_intersects (&node->bounds, &(children[i]->bounds));
|
|
graphene_rect_union (&node->bounds, &(children[i]->bounds), &node->bounds);
|
|
node->preferred_depth = gdk_memory_depth_merge (node->preferred_depth, children[i]->preferred_depth);
|
|
node->offscreen_for_opacity = node->offscreen_for_opacity || children[i]->offscreen_for_opacity;
|
|
if (gsk_render_node_get_opaque_rect (self->children[i], &child_opaque))
|
|
{
|
|
if (have_opaque)
|
|
gsk_rect_coverage (&self->opaque, &child_opaque, &self->opaque);
|
|
else
|
|
{
|
|
self->opaque = child_opaque;
|
|
have_opaque = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
node->offscreen_for_opacity = node->offscreen_for_opacity || !self->disjoint;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* gsk_container_node_get_n_children:
|
|
* @node: (type GskContainerNode): a container `GskRenderNode`
|
|
*
|
|
* Retrieves the number of direct children of @node.
|
|
*
|
|
* Returns: the number of children of the `GskRenderNode`
|
|
*/
|
|
guint
|
|
gsk_container_node_get_n_children (const GskRenderNode *node)
|
|
{
|
|
const GskContainerNode *self = (const GskContainerNode *) node;
|
|
|
|
return self->n_children;
|
|
}
|
|
|
|
/**
|
|
* gsk_container_node_get_child:
|
|
* @node: (type GskContainerNode): a container `GskRenderNode`
|
|
* @idx: the position of the child to get
|
|
*
|
|
* Gets one of the children of @container.
|
|
*
|
|
* Returns: (transfer none): the @idx'th child of @container
|
|
*/
|
|
GskRenderNode *
|
|
gsk_container_node_get_child (const GskRenderNode *node,
|
|
guint idx)
|
|
{
|
|
const GskContainerNode *self = (const GskContainerNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_CONTAINER_NODE), NULL);
|
|
g_return_val_if_fail (idx < self->n_children, NULL);
|
|
|
|
return self->children[idx];
|
|
}
|
|
|
|
GskRenderNode **
|
|
gsk_container_node_get_children (const GskRenderNode *node,
|
|
guint *n_children)
|
|
{
|
|
const GskContainerNode *self = (const GskContainerNode *) node;
|
|
|
|
*n_children = self->n_children;
|
|
|
|
return self->children;
|
|
}
|
|
|
|
/*< private>
|
|
* gsk_container_node_is_disjoint:
|
|
* @node: a container `GskRenderNode`
|
|
*
|
|
* Returns `TRUE` if it is known that the child nodes are not
|
|
* overlapping. There is no guarantee that they do overlap
|
|
* if this function return FALSE.
|
|
*
|
|
* Returns: `TRUE` if children don't overlap
|
|
*/
|
|
gboolean
|
|
gsk_container_node_is_disjoint (const GskRenderNode *node)
|
|
{
|
|
const GskContainerNode *self = (const GskContainerNode *) node;
|
|
|
|
return self->disjoint;
|
|
}
|
|
|
|
/* }}} */
|
|
/* {{{ GSK_TRANSFORM_NODE */
|
|
|
|
/**
|
|
* GskTransformNode:
|
|
*
|
|
* A render node applying a `GskTransform` to its single child node.
|
|
*/
|
|
struct _GskTransformNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
GskRenderNode *child;
|
|
GskTransform *transform;
|
|
float dx, dy;
|
|
};
|
|
|
|
static void
|
|
gsk_transform_node_finalize (GskRenderNode *node)
|
|
{
|
|
GskTransformNode *self = (GskTransformNode *) node;
|
|
GskRenderNodeClass *parent_class = g_type_class_peek (g_type_parent (GSK_TYPE_TRANSFORM_NODE));
|
|
|
|
gsk_render_node_unref (self->child);
|
|
gsk_transform_unref (self->transform);
|
|
|
|
parent_class->finalize (node);
|
|
}
|
|
|
|
static void
|
|
gsk_transform_node_draw (GskRenderNode *node,
|
|
cairo_t *cr,
|
|
GdkColorState *ccs)
|
|
{
|
|
GskTransformNode *self = (GskTransformNode *) node;
|
|
float xx, yx, xy, yy, dx, dy;
|
|
cairo_matrix_t ctm;
|
|
|
|
if (gsk_transform_get_category (self->transform) < GSK_TRANSFORM_CATEGORY_2D)
|
|
{
|
|
GdkRGBA pink = { 255 / 255., 105 / 255., 180 / 255., 1.0 };
|
|
gdk_cairo_set_source_rgba_ccs (cr, ccs, &pink);
|
|
gdk_cairo_rect (cr, &node->bounds);
|
|
cairo_fill (cr);
|
|
return;
|
|
}
|
|
|
|
gsk_transform_to_2d (self->transform, &xx, &yx, &xy, &yy, &dx, &dy);
|
|
cairo_matrix_init (&ctm, xx, yx, xy, yy, dx, dy);
|
|
if (xx * yy == xy * yx)
|
|
{
|
|
/* broken matrix here. This can happen during transitions
|
|
* (like when flipping an axis at the point where scale == 0)
|
|
* and just means that nothing should be drawn.
|
|
* But Cairo throws lots of ugly errors instead of silently
|
|
* going on. So we silently go on.
|
|
*/
|
|
return;
|
|
}
|
|
cairo_transform (cr, &ctm);
|
|
|
|
gsk_render_node_draw_ccs (self->child, cr, ccs);
|
|
}
|
|
|
|
static gboolean
|
|
gsk_transform_node_can_diff (const GskRenderNode *node1,
|
|
const GskRenderNode *node2)
|
|
{
|
|
GskTransformNode *self1 = (GskTransformNode *) node1;
|
|
GskTransformNode *self2 = (GskTransformNode *) node2;
|
|
|
|
if (!gsk_transform_equal (self1->transform, self2->transform))
|
|
return FALSE;
|
|
|
|
return gsk_render_node_can_diff (self1->child, self2->child);
|
|
}
|
|
|
|
static void
|
|
gsk_transform_node_diff (GskRenderNode *node1,
|
|
GskRenderNode *node2,
|
|
GskDiffData *data)
|
|
{
|
|
GskTransformNode *self1 = (GskTransformNode *) node1;
|
|
GskTransformNode *self2 = (GskTransformNode *) node2;
|
|
|
|
if (!gsk_transform_equal (self1->transform, self2->transform))
|
|
{
|
|
gsk_render_node_diff_impossible (node1, node2, data);
|
|
return;
|
|
}
|
|
|
|
if (self1->child == self2->child)
|
|
return;
|
|
|
|
switch (gsk_transform_get_category (self1->transform))
|
|
{
|
|
case GSK_TRANSFORM_CATEGORY_IDENTITY:
|
|
gsk_render_node_diff (self1->child, self2->child, data);
|
|
break;
|
|
|
|
case GSK_TRANSFORM_CATEGORY_2D_TRANSLATE:
|
|
{
|
|
cairo_region_t *sub;
|
|
float dx, dy;
|
|
gsk_transform_to_translate (self1->transform, &dx, &dy);
|
|
sub = cairo_region_create ();
|
|
gsk_render_node_diff (self1->child, self2->child, &(GskDiffData) { sub, data->surface });
|
|
cairo_region_translate (sub, floorf (dx), floorf (dy));
|
|
if (floorf (dx) != dx)
|
|
{
|
|
cairo_region_t *tmp = cairo_region_copy (sub);
|
|
cairo_region_translate (tmp, 1, 0);
|
|
cairo_region_union (sub, tmp);
|
|
cairo_region_destroy (tmp);
|
|
}
|
|
if (floorf (dy) != dy)
|
|
{
|
|
cairo_region_t *tmp = cairo_region_copy (sub);
|
|
cairo_region_translate (tmp, 0, 1);
|
|
cairo_region_union (sub, tmp);
|
|
cairo_region_destroy (tmp);
|
|
}
|
|
cairo_region_union (data->region, sub);
|
|
cairo_region_destroy (sub);
|
|
}
|
|
break;
|
|
|
|
case GSK_TRANSFORM_CATEGORY_2D_AFFINE:
|
|
{
|
|
cairo_region_t *sub;
|
|
float scale_x, scale_y, dx, dy;
|
|
gsk_transform_to_affine (self1->transform, &scale_x, &scale_y, &dx, &dy);
|
|
sub = cairo_region_create ();
|
|
gsk_render_node_diff (self1->child, self2->child, &(GskDiffData) { sub, data->surface });
|
|
region_union_region_affine (data->region, sub, scale_x, scale_y, dx, dy);
|
|
cairo_region_destroy (sub);
|
|
}
|
|
break;
|
|
|
|
case GSK_TRANSFORM_CATEGORY_UNKNOWN:
|
|
case GSK_TRANSFORM_CATEGORY_ANY:
|
|
case GSK_TRANSFORM_CATEGORY_3D:
|
|
case GSK_TRANSFORM_CATEGORY_2D:
|
|
default:
|
|
gsk_render_node_diff_impossible (node1, node2, data);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gsk_transform_node_get_opaque_rect (GskRenderNode *node,
|
|
graphene_rect_t *opaque)
|
|
{
|
|
GskTransformNode *self = (GskTransformNode *) node;
|
|
graphene_rect_t child_opaque;
|
|
|
|
if (gsk_transform_get_fine_category (self->transform) < GSK_FINE_TRANSFORM_CATEGORY_2D_DIHEDRAL)
|
|
return FALSE;
|
|
|
|
if (!gsk_render_node_get_opaque_rect (self->child, &child_opaque))
|
|
return FALSE;
|
|
|
|
gsk_transform_transform_bounds (self->transform,
|
|
&child_opaque,
|
|
opaque);
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gsk_transform_node_class_init (gpointer g_class,
|
|
gpointer class_data)
|
|
{
|
|
GskRenderNodeClass *node_class = g_class;
|
|
|
|
node_class->node_type = GSK_TRANSFORM_NODE;
|
|
|
|
node_class->finalize = gsk_transform_node_finalize;
|
|
node_class->draw = gsk_transform_node_draw;
|
|
node_class->can_diff = gsk_transform_node_can_diff;
|
|
node_class->diff = gsk_transform_node_diff;
|
|
node_class->get_opaque_rect = gsk_transform_node_get_opaque_rect;
|
|
}
|
|
|
|
/**
|
|
* gsk_transform_node_new:
|
|
* @child: The node to transform
|
|
* @transform: (transfer none): The transform to apply
|
|
*
|
|
* Creates a `GskRenderNode` that will transform the given @child
|
|
* with the given @transform.
|
|
*
|
|
* Returns: (transfer full) (type GskTransformNode): A new `GskRenderNode`
|
|
*/
|
|
GskRenderNode *
|
|
gsk_transform_node_new (GskRenderNode *child,
|
|
GskTransform *transform)
|
|
{
|
|
GskTransformNode *self;
|
|
GskRenderNode *node;
|
|
GskTransformCategory category;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE (child), NULL);
|
|
g_return_val_if_fail (transform != NULL, NULL);
|
|
|
|
category = gsk_transform_get_category (transform);
|
|
|
|
self = gsk_render_node_alloc (GSK_TRANSFORM_NODE);
|
|
node = (GskRenderNode *) self;
|
|
node->offscreen_for_opacity = child->offscreen_for_opacity;
|
|
node->fully_opaque = child->fully_opaque && category >= GSK_TRANSFORM_CATEGORY_2D_AFFINE;
|
|
|
|
self->child = gsk_render_node_ref (child);
|
|
self->transform = gsk_transform_ref (transform);
|
|
|
|
if (category >= GSK_TRANSFORM_CATEGORY_2D_TRANSLATE)
|
|
gsk_transform_to_translate (transform, &self->dx, &self->dy);
|
|
else
|
|
self->dx = self->dy = 0;
|
|
|
|
gsk_transform_transform_bounds (self->transform,
|
|
&child->bounds,
|
|
&node->bounds);
|
|
|
|
node->preferred_depth = gsk_render_node_get_preferred_depth (child);
|
|
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* gsk_transform_node_get_child:
|
|
* @node: (type GskTransformNode): a `GskRenderNode` for a transform
|
|
*
|
|
* Gets the child node that is getting transformed by the given @node.
|
|
*
|
|
* Returns: (transfer none): The child that is getting transformed
|
|
*/
|
|
GskRenderNode *
|
|
gsk_transform_node_get_child (const GskRenderNode *node)
|
|
{
|
|
const GskTransformNode *self = (const GskTransformNode *) node;
|
|
|
|
return self->child;
|
|
}
|
|
|
|
/**
|
|
* gsk_transform_node_get_transform:
|
|
* @node: (type GskTransformNode): a `GskRenderNode` for a transform
|
|
*
|
|
* Retrieves the `GskTransform` used by the @node.
|
|
*
|
|
* Returns: (transfer none): a `GskTransform`
|
|
*/
|
|
GskTransform *
|
|
gsk_transform_node_get_transform (const GskRenderNode *node)
|
|
{
|
|
const GskTransformNode *self = (const GskTransformNode *) node;
|
|
|
|
return self->transform;
|
|
}
|
|
|
|
void
|
|
gsk_transform_node_get_translate (const GskRenderNode *node,
|
|
float *dx,
|
|
float *dy)
|
|
{
|
|
const GskTransformNode *self = (const GskTransformNode *) node;
|
|
|
|
*dx = self->dx;
|
|
*dy = self->dy;
|
|
}
|
|
|
|
/* }}} */
|
|
/* {{{ GSK_OPACITY_NODE */
|
|
|
|
/**
|
|
* GskOpacityNode:
|
|
*
|
|
* A render node controlling the opacity of its single child node.
|
|
*/
|
|
struct _GskOpacityNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
GskRenderNode *child;
|
|
float opacity;
|
|
};
|
|
|
|
static void
|
|
gsk_opacity_node_finalize (GskRenderNode *node)
|
|
{
|
|
GskOpacityNode *self = (GskOpacityNode *) node;
|
|
GskRenderNodeClass *parent_class = g_type_class_peek (g_type_parent (GSK_TYPE_OPACITY_NODE));
|
|
|
|
gsk_render_node_unref (self->child);
|
|
|
|
parent_class->finalize (node);
|
|
}
|
|
|
|
static void
|
|
gsk_opacity_node_draw (GskRenderNode *node,
|
|
cairo_t *cr,
|
|
GdkColorState *ccs)
|
|
{
|
|
GskOpacityNode *self = (GskOpacityNode *) node;
|
|
|
|
/* clip so the push_group() creates a smaller surface */
|
|
gsk_cairo_rectangle_pixel_aligned (cr, &node->bounds);
|
|
cairo_clip (cr);
|
|
|
|
if (has_empty_clip (cr))
|
|
return;
|
|
|
|
cairo_push_group (cr);
|
|
|
|
gsk_render_node_draw_ccs (self->child, cr, ccs);
|
|
|
|
cairo_pop_group_to_source (cr);
|
|
cairo_paint_with_alpha (cr, self->opacity);
|
|
}
|
|
|
|
static void
|
|
gsk_opacity_node_diff (GskRenderNode *node1,
|
|
GskRenderNode *node2,
|
|
GskDiffData *data)
|
|
{
|
|
GskOpacityNode *self1 = (GskOpacityNode *) node1;
|
|
GskOpacityNode *self2 = (GskOpacityNode *) node2;
|
|
|
|
if (self1->opacity == self2->opacity)
|
|
gsk_render_node_diff (self1->child, self2->child, data);
|
|
else
|
|
gsk_render_node_diff_impossible (node1, node2, data);
|
|
}
|
|
|
|
static void
|
|
gsk_opacity_node_class_init (gpointer g_class,
|
|
gpointer class_data)
|
|
{
|
|
GskRenderNodeClass *node_class = g_class;
|
|
|
|
node_class->node_type = GSK_OPACITY_NODE;
|
|
|
|
node_class->finalize = gsk_opacity_node_finalize;
|
|
node_class->draw = gsk_opacity_node_draw;
|
|
node_class->diff = gsk_opacity_node_diff;
|
|
}
|
|
|
|
/**
|
|
* gsk_opacity_node_new:
|
|
* @child: The node to draw
|
|
* @opacity: The opacity to apply
|
|
*
|
|
* Creates a `GskRenderNode` that will drawn the @child with reduced
|
|
* @opacity.
|
|
*
|
|
* Returns: (transfer full) (type GskOpacityNode): A new `GskRenderNode`
|
|
*/
|
|
GskRenderNode *
|
|
gsk_opacity_node_new (GskRenderNode *child,
|
|
float opacity)
|
|
{
|
|
GskOpacityNode *self;
|
|
GskRenderNode *node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE (child), NULL);
|
|
|
|
self = gsk_render_node_alloc (GSK_OPACITY_NODE);
|
|
node = (GskRenderNode *) self;
|
|
node->offscreen_for_opacity = child->offscreen_for_opacity;
|
|
|
|
self->child = gsk_render_node_ref (child);
|
|
self->opacity = CLAMP (opacity, 0.0, 1.0);
|
|
|
|
gsk_rect_init_from_rect (&node->bounds, &child->bounds);
|
|
|
|
node->preferred_depth = gsk_render_node_get_preferred_depth (child);
|
|
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* gsk_opacity_node_get_child:
|
|
* @node: (type GskOpacityNode): a `GskRenderNode` for an opacity
|
|
*
|
|
* Gets the child node that is getting opacityed by the given @node.
|
|
*
|
|
* Returns: (transfer none): The child that is getting opacityed
|
|
*/
|
|
GskRenderNode *
|
|
gsk_opacity_node_get_child (const GskRenderNode *node)
|
|
{
|
|
const GskOpacityNode *self = (const GskOpacityNode *) node;
|
|
|
|
return self->child;
|
|
}
|
|
|
|
/**
|
|
* gsk_opacity_node_get_opacity:
|
|
* @node: (type GskOpacityNode): a `GskRenderNode` for an opacity
|
|
*
|
|
* Gets the transparency factor for an opacity node.
|
|
*
|
|
* Returns: the opacity factor
|
|
*/
|
|
float
|
|
gsk_opacity_node_get_opacity (const GskRenderNode *node)
|
|
{
|
|
const GskOpacityNode *self = (const GskOpacityNode *) node;
|
|
|
|
return self->opacity;
|
|
}
|
|
|
|
/* }}} */
|
|
/* {{{ GSK_COLOR_MATRIX_NODE */
|
|
|
|
/**
|
|
* GskColorMatrixNode:
|
|
*
|
|
* A render node controlling the color matrix of its single child node.
|
|
*/
|
|
struct _GskColorMatrixNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
GskRenderNode *child;
|
|
graphene_matrix_t color_matrix;
|
|
graphene_vec4_t color_offset;
|
|
};
|
|
|
|
static void
|
|
gsk_color_matrix_node_finalize (GskRenderNode *node)
|
|
{
|
|
GskColorMatrixNode *self = (GskColorMatrixNode *) node;
|
|
GskRenderNodeClass *parent_class = g_type_class_peek (g_type_parent (GSK_TYPE_COLOR_MATRIX_NODE));
|
|
|
|
gsk_render_node_unref (self->child);
|
|
|
|
parent_class->finalize (node);
|
|
}
|
|
|
|
static void
|
|
apply_color_matrix_to_pattern (cairo_pattern_t *pattern,
|
|
const graphene_matrix_t *color_matrix,
|
|
const graphene_vec4_t *color_offset)
|
|
{
|
|
cairo_surface_t *surface, *image_surface;
|
|
guchar *data;
|
|
gsize x, y, width, height, stride;
|
|
float alpha;
|
|
graphene_vec4_t pixel;
|
|
guint32* pixel_data;
|
|
|
|
cairo_pattern_get_surface (pattern, &surface);
|
|
image_surface = cairo_surface_map_to_image (surface, NULL);
|
|
|
|
data = cairo_image_surface_get_data (image_surface);
|
|
width = cairo_image_surface_get_width (image_surface);
|
|
height = cairo_image_surface_get_height (image_surface);
|
|
stride = cairo_image_surface_get_stride (image_surface);
|
|
|
|
for (y = 0; y < height; y++)
|
|
{
|
|
pixel_data = (guint32 *) data;
|
|
for (x = 0; x < width; x++)
|
|
{
|
|
alpha = ((pixel_data[x] >> 24) & 0xFF) / 255.0;
|
|
|
|
if (alpha == 0)
|
|
{
|
|
graphene_vec4_init (&pixel, 0.0, 0.0, 0.0, 0.0);
|
|
}
|
|
else
|
|
{
|
|
graphene_vec4_init (&pixel,
|
|
((pixel_data[x] >> 16) & 0xFF) / (255.0 * alpha),
|
|
((pixel_data[x] >> 8) & 0xFF) / (255.0 * alpha),
|
|
( pixel_data[x] & 0xFF) / (255.0 * alpha),
|
|
alpha);
|
|
graphene_matrix_transform_vec4 (color_matrix, &pixel, &pixel);
|
|
}
|
|
|
|
graphene_vec4_add (&pixel, color_offset, &pixel);
|
|
|
|
alpha = graphene_vec4_get_w (&pixel);
|
|
|
|
if (alpha > 0.0)
|
|
{
|
|
alpha = MIN (alpha, 1.0);
|
|
pixel_data[x] = (((guint32) roundf (alpha * 255)) << 24) |
|
|
(((guint32) roundf (CLAMP (graphene_vec4_get_x (&pixel), 0, 1) * alpha * 255)) << 16) |
|
|
(((guint32) roundf (CLAMP (graphene_vec4_get_y (&pixel), 0, 1) * alpha * 255)) << 8) |
|
|
((guint32) roundf (CLAMP (graphene_vec4_get_z (&pixel), 0, 1) * alpha * 255));
|
|
}
|
|
else
|
|
{
|
|
pixel_data[x] = 0;
|
|
}
|
|
}
|
|
data += stride;
|
|
}
|
|
|
|
cairo_surface_mark_dirty (image_surface);
|
|
cairo_surface_unmap_image (surface, image_surface);
|
|
/* https://gitlab.freedesktop.org/cairo/cairo/-/merge_requests/487 */
|
|
cairo_surface_mark_dirty (surface);
|
|
}
|
|
|
|
static void
|
|
gsk_color_matrix_node_draw (GskRenderNode *node,
|
|
cairo_t *cr,
|
|
GdkColorState *ccs)
|
|
{
|
|
GskColorMatrixNode *self = (GskColorMatrixNode *) node;
|
|
cairo_pattern_t *pattern;
|
|
|
|
/* clip so the push_group() creates a smaller surface */
|
|
gdk_cairo_rect (cr, &node->bounds);
|
|
cairo_clip (cr);
|
|
|
|
if (has_empty_clip (cr))
|
|
return;
|
|
|
|
cairo_push_group (cr);
|
|
|
|
gsk_render_node_draw_ccs (self->child, cr, ccs);
|
|
|
|
pattern = cairo_pop_group (cr);
|
|
apply_color_matrix_to_pattern (pattern, &self->color_matrix, &self->color_offset);
|
|
|
|
cairo_set_source (cr, pattern);
|
|
cairo_paint (cr);
|
|
|
|
cairo_pattern_destroy (pattern);
|
|
}
|
|
|
|
static void
|
|
gsk_color_matrix_node_diff (GskRenderNode *node1,
|
|
GskRenderNode *node2,
|
|
GskDiffData *data)
|
|
{
|
|
GskColorMatrixNode *self1 = (GskColorMatrixNode *) node1;
|
|
GskColorMatrixNode *self2 = (GskColorMatrixNode *) node2;
|
|
|
|
if (!graphene_vec4_equal (&self1->color_offset, &self2->color_offset))
|
|
goto nope;
|
|
|
|
if (!graphene_matrix_equal_fast (&self1->color_matrix, &self2->color_matrix))
|
|
goto nope;
|
|
|
|
gsk_render_node_diff (self1->child, self2->child, data);
|
|
return;
|
|
|
|
nope:
|
|
gsk_render_node_diff_impossible (node1, node2, data);
|
|
return;
|
|
}
|
|
|
|
static void
|
|
gsk_color_matrix_node_class_init (gpointer g_class,
|
|
gpointer class_data)
|
|
{
|
|
GskRenderNodeClass *node_class = g_class;
|
|
|
|
node_class->node_type = GSK_COLOR_MATRIX_NODE;
|
|
|
|
node_class->finalize = gsk_color_matrix_node_finalize;
|
|
node_class->draw = gsk_color_matrix_node_draw;
|
|
node_class->diff = gsk_color_matrix_node_diff;
|
|
}
|
|
|
|
/**
|
|
* gsk_color_matrix_node_new:
|
|
* @child: The node to draw
|
|
* @color_matrix: The matrix to apply
|
|
* @color_offset: Values to add to the color
|
|
*
|
|
* Creates a `GskRenderNode` that will drawn the @child with
|
|
* @color_matrix.
|
|
*
|
|
* In particular, the node will transform colors by applying
|
|
*
|
|
* pixel = transpose(color_matrix) * pixel + color_offset
|
|
*
|
|
* for every pixel. The transformation operates on unpremultiplied
|
|
* colors, with color components ordered R, G, B, A.
|
|
*
|
|
* Returns: (transfer full) (type GskColorMatrixNode): A new `GskRenderNode`
|
|
*/
|
|
GskRenderNode *
|
|
gsk_color_matrix_node_new (GskRenderNode *child,
|
|
const graphene_matrix_t *color_matrix,
|
|
const graphene_vec4_t *color_offset)
|
|
{
|
|
GskColorMatrixNode *self;
|
|
GskRenderNode *node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE (child), NULL);
|
|
|
|
self = gsk_render_node_alloc (GSK_COLOR_MATRIX_NODE);
|
|
node = (GskRenderNode *) self;
|
|
node->offscreen_for_opacity = child->offscreen_for_opacity;
|
|
|
|
self->child = gsk_render_node_ref (child);
|
|
graphene_matrix_init_from_matrix (&self->color_matrix, color_matrix);
|
|
graphene_vec4_init_from_vec4 (&self->color_offset, color_offset);
|
|
|
|
gsk_rect_init_from_rect (&node->bounds, &child->bounds);
|
|
|
|
node->preferred_depth = gsk_render_node_get_preferred_depth (child);
|
|
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* gsk_color_matrix_node_get_child:
|
|
* @node: (type GskColorMatrixNode): a color matrix `GskRenderNode`
|
|
*
|
|
* Gets the child node that is getting its colors modified by the given @node.
|
|
*
|
|
* Returns: (transfer none): The child that is getting its colors modified
|
|
**/
|
|
GskRenderNode *
|
|
gsk_color_matrix_node_get_child (const GskRenderNode *node)
|
|
{
|
|
const GskColorMatrixNode *self = (const GskColorMatrixNode *) node;
|
|
|
|
return self->child;
|
|
}
|
|
|
|
/**
|
|
* gsk_color_matrix_node_get_color_matrix:
|
|
* @node: (type GskColorMatrixNode): a color matrix `GskRenderNode`
|
|
*
|
|
* Retrieves the color matrix used by the @node.
|
|
*
|
|
* Returns: a 4x4 color matrix
|
|
*/
|
|
const graphene_matrix_t *
|
|
gsk_color_matrix_node_get_color_matrix (const GskRenderNode *node)
|
|
{
|
|
const GskColorMatrixNode *self = (const GskColorMatrixNode *) node;
|
|
|
|
return &self->color_matrix;
|
|
}
|
|
|
|
/**
|
|
* gsk_color_matrix_node_get_color_offset:
|
|
* @node: (type GskColorMatrixNode): a color matrix `GskRenderNode`
|
|
*
|
|
* Retrieves the color offset used by the @node.
|
|
*
|
|
* Returns: a color vector
|
|
*/
|
|
const graphene_vec4_t *
|
|
gsk_color_matrix_node_get_color_offset (const GskRenderNode *node)
|
|
{
|
|
const GskColorMatrixNode *self = (const GskColorMatrixNode *) node;
|
|
|
|
return &self->color_offset;
|
|
}
|
|
|
|
/* }}} */
|
|
/* {{{ GSK_REPEAT_NODE */
|
|
|
|
/**
|
|
* GskRepeatNode:
|
|
*
|
|
* A render node repeating its single child node.
|
|
*/
|
|
struct _GskRepeatNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
GskRenderNode *child;
|
|
graphene_rect_t child_bounds;
|
|
};
|
|
|
|
static void
|
|
gsk_repeat_node_finalize (GskRenderNode *node)
|
|
{
|
|
GskRepeatNode *self = (GskRepeatNode *) node;
|
|
GskRenderNodeClass *parent_class = g_type_class_peek (g_type_parent (GSK_TYPE_REPEAT_NODE));
|
|
|
|
gsk_render_node_unref (self->child);
|
|
|
|
parent_class->finalize (node);
|
|
}
|
|
|
|
static void
|
|
gsk_repeat_node_draw_tiled (cairo_t *cr,
|
|
GdkColorState *ccs,
|
|
const graphene_rect_t *rect,
|
|
float x,
|
|
float y,
|
|
GskRenderNode *child,
|
|
const graphene_rect_t *child_bounds)
|
|
{
|
|
cairo_pattern_t *pattern;
|
|
cairo_matrix_t matrix;
|
|
|
|
cairo_save (cr);
|
|
/* reset the clip so we get an unclipped pattern for repeating */
|
|
cairo_reset_clip (cr);
|
|
cairo_translate (cr,
|
|
x * child_bounds->size.width,
|
|
y * child_bounds->size.height);
|
|
gdk_cairo_rect (cr, child_bounds);
|
|
cairo_clip (cr);
|
|
|
|
cairo_push_group (cr);
|
|
gsk_render_node_draw_ccs (child, cr, ccs);
|
|
pattern = cairo_pop_group (cr);
|
|
cairo_restore (cr);
|
|
|
|
cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
|
|
cairo_pattern_get_matrix (pattern, &matrix);
|
|
cairo_matrix_translate (&matrix,
|
|
- x * child_bounds->size.width,
|
|
- y * child_bounds->size.height);
|
|
cairo_pattern_set_matrix (pattern, &matrix);
|
|
cairo_set_source (cr, pattern);
|
|
cairo_pattern_destroy (pattern);
|
|
|
|
gdk_cairo_rect (cr, rect);
|
|
cairo_fill (cr);
|
|
}
|
|
|
|
static void
|
|
gsk_repeat_node_draw (GskRenderNode *node,
|
|
cairo_t *cr,
|
|
GdkColorState *ccs)
|
|
{
|
|
GskRepeatNode *self = (GskRepeatNode *) node;
|
|
graphene_rect_t clip_bounds;
|
|
float tile_left, tile_right, tile_top, tile_bottom;
|
|
|
|
gsk_cairo_rectangle_pixel_aligned (cr, &node->bounds);
|
|
cairo_clip (cr);
|
|
_graphene_rect_init_from_clip_extents (&clip_bounds, cr);
|
|
|
|
tile_left = (clip_bounds.origin.x - self->child_bounds.origin.x) / self->child_bounds.size.width;
|
|
tile_right = (clip_bounds.origin.x + clip_bounds.size.width - self->child_bounds.origin.x) / self->child_bounds.size.width;
|
|
tile_top = (clip_bounds.origin.y - self->child_bounds.origin.y) / self->child_bounds.size.height;
|
|
tile_bottom = (clip_bounds.origin.y + clip_bounds.size.height - self->child_bounds.origin.y) / self->child_bounds.size.height;
|
|
|
|
/* the 1st check tests that a tile fully fits into the bounds,
|
|
* the 2nd check is to catch the case where it fits exactly */
|
|
if (ceilf (tile_left) < floorf (tile_right) &&
|
|
clip_bounds.size.width > self->child_bounds.size.width)
|
|
{
|
|
if (ceilf (tile_top) < floorf (tile_bottom) &&
|
|
clip_bounds.size.height > self->child_bounds.size.height)
|
|
{
|
|
/* tile in both directions */
|
|
gsk_repeat_node_draw_tiled (cr,
|
|
ccs,
|
|
&clip_bounds,
|
|
ceilf (tile_left),
|
|
ceilf (tile_top),
|
|
self->child,
|
|
&self->child_bounds);
|
|
}
|
|
else
|
|
{
|
|
/* tile horizontally, repeat vertically */
|
|
float y;
|
|
for (y = floorf (tile_top); y < ceilf (tile_bottom); y++)
|
|
{
|
|
float start_y = MAX (clip_bounds.origin.y,
|
|
self->child_bounds.origin.y + y * self->child_bounds.size.height);
|
|
float end_y = MAX (clip_bounds.origin.y + clip_bounds.size.height,
|
|
self->child_bounds.origin.y + (y + 1) * self->child_bounds.size.height);
|
|
gsk_repeat_node_draw_tiled (cr,
|
|
ccs,
|
|
&GRAPHENE_RECT_INIT (
|
|
clip_bounds.origin.x,
|
|
start_y,
|
|
clip_bounds.size.width,
|
|
end_y - start_y
|
|
),
|
|
ceilf (tile_left),
|
|
y,
|
|
self->child,
|
|
&self->child_bounds);
|
|
}
|
|
}
|
|
}
|
|
else if (ceilf (tile_top) < floorf (tile_bottom) &&
|
|
clip_bounds.size.height > self->child_bounds.size.height)
|
|
{
|
|
/* repeat horizontally, tile vertically */
|
|
float x;
|
|
for (x = floorf (tile_left); x < ceilf (tile_right); x++)
|
|
{
|
|
float start_x = MAX (clip_bounds.origin.x,
|
|
self->child_bounds.origin.x + x * self->child_bounds.size.width);
|
|
float end_x = MAX (clip_bounds.origin.x + clip_bounds.size.width,
|
|
self->child_bounds.origin.x + (x + 1) * self->child_bounds.size.width);
|
|
gsk_repeat_node_draw_tiled (cr,
|
|
ccs,
|
|
&GRAPHENE_RECT_INIT (
|
|
start_x,
|
|
clip_bounds.origin.y,
|
|
end_x - start_x,
|
|
clip_bounds.size.height
|
|
),
|
|
x,
|
|
ceilf (tile_top),
|
|
self->child,
|
|
&self->child_bounds);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* repeat in both directions */
|
|
float x, y;
|
|
|
|
for (x = floorf (tile_left); x < ceilf (tile_right); x++)
|
|
{
|
|
for (y = floorf (tile_top); y < ceilf (tile_bottom); y++)
|
|
{
|
|
cairo_save (cr);
|
|
cairo_translate (cr,
|
|
x * self->child_bounds.size.width,
|
|
y * self->child_bounds.size.height);
|
|
gdk_cairo_rect (cr, &self->child_bounds);
|
|
cairo_clip (cr);
|
|
gsk_render_node_draw_ccs (self->child, cr, ccs);
|
|
cairo_restore (cr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
gsk_repeat_node_diff (GskRenderNode *node1,
|
|
GskRenderNode *node2,
|
|
GskDiffData *data)
|
|
{
|
|
GskRepeatNode *self1 = (GskRepeatNode *) node1;
|
|
GskRepeatNode *self2 = (GskRepeatNode *) node2;
|
|
|
|
if (gsk_rect_equal (&node1->bounds, &node2->bounds) &&
|
|
gsk_rect_equal (&self1->child_bounds, &self2->child_bounds))
|
|
{
|
|
cairo_region_t *sub;
|
|
|
|
sub = cairo_region_create();
|
|
gsk_render_node_diff (self1->child, self2->child, &(GskDiffData) { sub, data->surface });
|
|
if (cairo_region_is_empty (sub))
|
|
{
|
|
cairo_region_destroy (sub);
|
|
return;
|
|
}
|
|
cairo_region_destroy (sub);
|
|
}
|
|
|
|
gsk_render_node_diff_impossible (node1, node2, data);
|
|
}
|
|
|
|
static void
|
|
gsk_repeat_node_class_init (gpointer g_class,
|
|
gpointer class_data)
|
|
{
|
|
GskRenderNodeClass *node_class = g_class;
|
|
|
|
node_class->node_type = GSK_REPEAT_NODE;
|
|
|
|
node_class->finalize = gsk_repeat_node_finalize;
|
|
node_class->draw = gsk_repeat_node_draw;
|
|
node_class->diff = gsk_repeat_node_diff;
|
|
}
|
|
|
|
/**
|
|
* gsk_repeat_node_new:
|
|
* @bounds: The bounds of the area to be painted
|
|
* @child: The child to repeat
|
|
* @child_bounds: (nullable): The area of the child to repeat or %NULL to
|
|
* use the child's bounds
|
|
*
|
|
* Creates a `GskRenderNode` that will repeat the drawing of @child across
|
|
* the given @bounds.
|
|
*
|
|
* Returns: (transfer full) (type GskRepeatNode): A new `GskRenderNode`
|
|
*/
|
|
GskRenderNode *
|
|
gsk_repeat_node_new (const graphene_rect_t *bounds,
|
|
GskRenderNode *child,
|
|
const graphene_rect_t *child_bounds)
|
|
{
|
|
GskRepeatNode *self;
|
|
GskRenderNode *node;
|
|
|
|
g_return_val_if_fail (bounds != NULL, NULL);
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE (child), NULL);
|
|
|
|
self = gsk_render_node_alloc (GSK_REPEAT_NODE);
|
|
node = (GskRenderNode *) self;
|
|
node->offscreen_for_opacity = TRUE;
|
|
|
|
gsk_rect_init_from_rect (&node->bounds, bounds);
|
|
gsk_rect_normalize (&node->bounds);
|
|
|
|
self->child = gsk_render_node_ref (child);
|
|
|
|
if (child_bounds)
|
|
{
|
|
gsk_rect_init_from_rect (&self->child_bounds, child_bounds);
|
|
gsk_rect_normalize (&self->child_bounds);
|
|
}
|
|
else
|
|
{
|
|
gsk_rect_init_from_rect (&self->child_bounds, &child->bounds);
|
|
}
|
|
|
|
node->preferred_depth = gsk_render_node_get_preferred_depth (child);
|
|
node->fully_opaque = child->fully_opaque && gsk_rect_contains_rect (&child->bounds, &self->child_bounds);
|
|
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* gsk_repeat_node_get_child:
|
|
* @node: (type GskRepeatNode): a repeat `GskRenderNode`
|
|
*
|
|
* Retrieves the child of @node.
|
|
*
|
|
* Returns: (transfer none): a `GskRenderNode`
|
|
*/
|
|
GskRenderNode *
|
|
gsk_repeat_node_get_child (const GskRenderNode *node)
|
|
{
|
|
const GskRepeatNode *self = (const GskRepeatNode *) node;
|
|
|
|
return self->child;
|
|
}
|
|
|
|
/**
|
|
* gsk_repeat_node_get_child_bounds:
|
|
* @node: (type GskRepeatNode): a repeat `GskRenderNode`
|
|
*
|
|
* Retrieves the bounding rectangle of the child of @node.
|
|
*
|
|
* Returns: (transfer none): a bounding rectangle
|
|
*/
|
|
const graphene_rect_t *
|
|
gsk_repeat_node_get_child_bounds (const GskRenderNode *node)
|
|
{
|
|
const GskRepeatNode *self = (const GskRepeatNode *) node;
|
|
|
|
return &self->child_bounds;
|
|
}
|
|
|
|
/* }}} */
|
|
/* {{{ GSK_CLIP_NODE */
|
|
|
|
/**
|
|
* GskClipNode:
|
|
*
|
|
* A render node applying a rectangular clip to its single child node.
|
|
*/
|
|
struct _GskClipNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
GskRenderNode *child;
|
|
graphene_rect_t clip;
|
|
};
|
|
|
|
static void
|
|
gsk_clip_node_finalize (GskRenderNode *node)
|
|
{
|
|
GskClipNode *self = (GskClipNode *) node;
|
|
GskRenderNodeClass *parent_class = g_type_class_peek (g_type_parent (GSK_TYPE_CLIP_NODE));
|
|
|
|
gsk_render_node_unref (self->child);
|
|
|
|
parent_class->finalize (node);
|
|
}
|
|
|
|
static void
|
|
gsk_clip_node_draw (GskRenderNode *node,
|
|
cairo_t *cr,
|
|
GdkColorState *ccs)
|
|
{
|
|
GskClipNode *self = (GskClipNode *) node;
|
|
|
|
cairo_save (cr);
|
|
|
|
gdk_cairo_rect (cr, &self->clip);
|
|
cairo_clip (cr);
|
|
|
|
gsk_render_node_draw_ccs (self->child, cr, ccs);
|
|
|
|
cairo_restore (cr);
|
|
}
|
|
|
|
static void
|
|
gsk_clip_node_diff (GskRenderNode *node1,
|
|
GskRenderNode *node2,
|
|
GskDiffData *data)
|
|
{
|
|
GskClipNode *self1 = (GskClipNode *) node1;
|
|
GskClipNode *self2 = (GskClipNode *) node2;
|
|
|
|
if (gsk_rect_equal (&self1->clip, &self2->clip))
|
|
{
|
|
cairo_region_t *sub;
|
|
cairo_rectangle_int_t clip_rect;
|
|
|
|
sub = cairo_region_create();
|
|
gsk_render_node_diff (self1->child, self2->child, &(GskDiffData) { sub, data->surface });
|
|
gsk_rect_to_cairo_grow (&self1->clip, &clip_rect);
|
|
cairo_region_intersect_rectangle (sub, &clip_rect);
|
|
cairo_region_union (data->region, sub);
|
|
cairo_region_destroy (sub);
|
|
}
|
|
else
|
|
{
|
|
gsk_render_node_diff_impossible (node1, node2, data);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gsk_clip_node_get_opaque_rect (GskRenderNode *node,
|
|
graphene_rect_t *opaque)
|
|
{
|
|
GskClipNode *self = (GskClipNode *) node;
|
|
graphene_rect_t child_opaque;
|
|
|
|
if (!gsk_render_node_get_opaque_rect (self->child, &child_opaque))
|
|
return FALSE;
|
|
|
|
return graphene_rect_intersection (&self->clip, &child_opaque, opaque);
|
|
}
|
|
|
|
static void
|
|
gsk_clip_node_class_init (gpointer g_class,
|
|
gpointer class_data)
|
|
{
|
|
GskRenderNodeClass *node_class = g_class;
|
|
|
|
node_class->node_type = GSK_CLIP_NODE;
|
|
|
|
node_class->finalize = gsk_clip_node_finalize;
|
|
node_class->draw = gsk_clip_node_draw;
|
|
node_class->diff = gsk_clip_node_diff;
|
|
node_class->get_opaque_rect = gsk_clip_node_get_opaque_rect;
|
|
}
|
|
|
|
/**
|
|
* gsk_clip_node_new:
|
|
* @child: The node to draw
|
|
* @clip: The clip to apply
|
|
*
|
|
* Creates a `GskRenderNode` that will clip the @child to the area
|
|
* given by @clip.
|
|
*
|
|
* Returns: (transfer full) (type GskClipNode): A new `GskRenderNode`
|
|
*/
|
|
GskRenderNode *
|
|
gsk_clip_node_new (GskRenderNode *child,
|
|
const graphene_rect_t *clip)
|
|
{
|
|
GskClipNode *self;
|
|
GskRenderNode *node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE (child), NULL);
|
|
g_return_val_if_fail (clip != NULL, NULL);
|
|
|
|
self = gsk_render_node_alloc (GSK_CLIP_NODE);
|
|
node = (GskRenderNode *) self;
|
|
node->offscreen_for_opacity = child->offscreen_for_opacity;
|
|
/* because of the intersection when computing bounds */
|
|
node->fully_opaque = child->fully_opaque;
|
|
|
|
self->child = gsk_render_node_ref (child);
|
|
gsk_rect_init_from_rect (&self->clip, clip);
|
|
gsk_rect_normalize (&self->clip);
|
|
|
|
gsk_rect_intersection (&self->clip, &child->bounds, &node->bounds);
|
|
|
|
node->preferred_depth = gsk_render_node_get_preferred_depth (child);
|
|
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* gsk_clip_node_get_child:
|
|
* @node: (type GskClipNode): a clip @GskRenderNode
|
|
*
|
|
* Gets the child node that is getting clipped by the given @node.
|
|
*
|
|
* Returns: (transfer none): The child that is getting clipped
|
|
**/
|
|
GskRenderNode *
|
|
gsk_clip_node_get_child (const GskRenderNode *node)
|
|
{
|
|
const GskClipNode *self = (const GskClipNode *) node;
|
|
|
|
return self->child;
|
|
}
|
|
|
|
/**
|
|
* gsk_clip_node_get_clip:
|
|
* @node: (type GskClipNode): a `GskClipNode`
|
|
*
|
|
* Retrieves the clip rectangle for @node.
|
|
*
|
|
* Returns: a clip rectangle
|
|
*/
|
|
const graphene_rect_t *
|
|
gsk_clip_node_get_clip (const GskRenderNode *node)
|
|
{
|
|
const GskClipNode *self = (const GskClipNode *) node;
|
|
|
|
return &self->clip;
|
|
}
|
|
|
|
/* }}} */
|
|
/* {{{ GSK_ROUNDED_CLIP_NODE */
|
|
|
|
/**
|
|
* GskRoundedClipNode:
|
|
*
|
|
* A render node applying a rounded rectangle clip to its single child.
|
|
*/
|
|
struct _GskRoundedClipNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
GskRenderNode *child;
|
|
GskRoundedRect clip;
|
|
};
|
|
|
|
static void
|
|
gsk_rounded_clip_node_finalize (GskRenderNode *node)
|
|
{
|
|
GskRoundedClipNode *self = (GskRoundedClipNode *) node;
|
|
GskRenderNodeClass *parent_class = g_type_class_peek (g_type_parent (GSK_TYPE_ROUNDED_CLIP_NODE));
|
|
|
|
gsk_render_node_unref (self->child);
|
|
|
|
parent_class->finalize (node);
|
|
}
|
|
|
|
static void
|
|
gsk_rounded_clip_node_draw (GskRenderNode *node,
|
|
cairo_t *cr,
|
|
GdkColorState *ccs)
|
|
{
|
|
GskRoundedClipNode *self = (GskRoundedClipNode *) node;
|
|
|
|
cairo_save (cr);
|
|
|
|
gsk_rounded_rect_path (&self->clip, cr);
|
|
cairo_clip (cr);
|
|
|
|
gsk_render_node_draw_ccs (self->child, cr, ccs);
|
|
|
|
cairo_restore (cr);
|
|
}
|
|
|
|
static void
|
|
gsk_rounded_clip_node_diff (GskRenderNode *node1,
|
|
GskRenderNode *node2,
|
|
GskDiffData *data)
|
|
{
|
|
GskRoundedClipNode *self1 = (GskRoundedClipNode *) node1;
|
|
GskRoundedClipNode *self2 = (GskRoundedClipNode *) node2;
|
|
|
|
if (gsk_rounded_rect_equal (&self1->clip, &self2->clip))
|
|
{
|
|
cairo_region_t *sub;
|
|
cairo_rectangle_int_t clip_rect;
|
|
|
|
sub = cairo_region_create();
|
|
gsk_render_node_diff (self1->child, self2->child, &(GskDiffData) { sub, data->surface });
|
|
gsk_rect_to_cairo_grow (&self1->clip.bounds, &clip_rect);
|
|
cairo_region_intersect_rectangle (sub, &clip_rect);
|
|
cairo_region_union (data->region, sub);
|
|
cairo_region_destroy (sub);
|
|
}
|
|
else
|
|
{
|
|
gsk_render_node_diff_impossible (node1, node2, data);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gsk_rounded_clip_node_get_opaque_rect (GskRenderNode *node,
|
|
graphene_rect_t *opaque)
|
|
{
|
|
GskRoundedClipNode *self = (GskRoundedClipNode *) node;
|
|
graphene_rect_t child_opaque, wide_opaque, high_opaque;
|
|
double start, end;
|
|
|
|
if (!gsk_render_node_get_opaque_rect (self->child, &child_opaque))
|
|
return FALSE;
|
|
|
|
wide_opaque = self->clip.bounds;
|
|
start = MAX(self->clip.corner[GSK_CORNER_TOP_LEFT].height, self->clip.corner[GSK_CORNER_TOP_RIGHT].height);
|
|
end = MAX(self->clip.corner[GSK_CORNER_BOTTOM_LEFT].height, self->clip.corner[GSK_CORNER_BOTTOM_RIGHT].height);
|
|
wide_opaque.size.height -= MIN (wide_opaque.size.height, start + end);
|
|
wide_opaque.origin.y += start;
|
|
graphene_rect_intersection (&wide_opaque, &child_opaque, &wide_opaque);
|
|
|
|
high_opaque = self->clip.bounds;
|
|
start = MAX(self->clip.corner[GSK_CORNER_TOP_LEFT].width, self->clip.corner[GSK_CORNER_BOTTOM_LEFT].width);
|
|
end = MAX(self->clip.corner[GSK_CORNER_TOP_RIGHT].width, self->clip.corner[GSK_CORNER_BOTTOM_RIGHT].width);
|
|
high_opaque.size.width -= MIN (high_opaque.size.width, start + end);
|
|
high_opaque.origin.x += start;
|
|
graphene_rect_intersection (&high_opaque, &child_opaque, &high_opaque);
|
|
|
|
if (wide_opaque.size.width * wide_opaque.size.height > high_opaque.size.width * high_opaque.size.height)
|
|
*opaque = wide_opaque;
|
|
else
|
|
*opaque = high_opaque;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gsk_rounded_clip_node_class_init (gpointer g_class,
|
|
gpointer class_data)
|
|
{
|
|
GskRenderNodeClass *node_class = g_class;
|
|
|
|
node_class->node_type = GSK_ROUNDED_CLIP_NODE;
|
|
|
|
node_class->finalize = gsk_rounded_clip_node_finalize;
|
|
node_class->draw = gsk_rounded_clip_node_draw;
|
|
node_class->diff = gsk_rounded_clip_node_diff;
|
|
node_class->get_opaque_rect = gsk_rounded_clip_node_get_opaque_rect;
|
|
}
|
|
|
|
/**
|
|
* gsk_rounded_clip_node_new:
|
|
* @child: The node to draw
|
|
* @clip: The clip to apply
|
|
*
|
|
* Creates a `GskRenderNode` that will clip the @child to the area
|
|
* given by @clip.
|
|
*
|
|
* Returns: (transfer full) (type GskRoundedClipNode): A new `GskRenderNode`
|
|
*/
|
|
GskRenderNode *
|
|
gsk_rounded_clip_node_new (GskRenderNode *child,
|
|
const GskRoundedRect *clip)
|
|
{
|
|
GskRoundedClipNode *self;
|
|
GskRenderNode *node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE (child), NULL);
|
|
g_return_val_if_fail (clip != NULL, NULL);
|
|
|
|
self = gsk_render_node_alloc (GSK_ROUNDED_CLIP_NODE);
|
|
node = (GskRenderNode *) self;
|
|
node->offscreen_for_opacity = child->offscreen_for_opacity;
|
|
|
|
self->child = gsk_render_node_ref (child);
|
|
gsk_rounded_rect_init_copy (&self->clip, clip);
|
|
|
|
gsk_rect_intersection (&self->clip.bounds, &child->bounds, &node->bounds);
|
|
|
|
node->preferred_depth = gsk_render_node_get_preferred_depth (child);
|
|
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* gsk_rounded_clip_node_get_child:
|
|
* @node: (type GskRoundedClipNode): a rounded clip `GskRenderNode`
|
|
*
|
|
* Gets the child node that is getting clipped by the given @node.
|
|
*
|
|
* Returns: (transfer none): The child that is getting clipped
|
|
**/
|
|
GskRenderNode *
|
|
gsk_rounded_clip_node_get_child (const GskRenderNode *node)
|
|
{
|
|
const GskRoundedClipNode *self = (const GskRoundedClipNode *) node;
|
|
|
|
return self->child;
|
|
}
|
|
|
|
/**
|
|
* gsk_rounded_clip_node_get_clip:
|
|
* @node: (type GskRoundedClipNode): a rounded clip `GskRenderNode`
|
|
*
|
|
* Retrieves the rounded rectangle used to clip the contents of the @node.
|
|
*
|
|
* Returns: (transfer none): a rounded rectangle
|
|
*/
|
|
const GskRoundedRect *
|
|
gsk_rounded_clip_node_get_clip (const GskRenderNode *node)
|
|
{
|
|
const GskRoundedClipNode *self = (const GskRoundedClipNode *) node;
|
|
|
|
return &self->clip;
|
|
}
|
|
|
|
/* }}} */
|
|
/* {{{ GSK_FILL_NODE */
|
|
|
|
/**
|
|
* GskFillNode:
|
|
*
|
|
* A render node filling the area given by [struct@Gsk.Path]
|
|
* and [enum@Gsk.FillRule] with the child node.
|
|
*
|
|
* Since: 4.14
|
|
*/
|
|
|
|
struct _GskFillNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
GskRenderNode *child;
|
|
GskPath *path;
|
|
GskFillRule fill_rule;
|
|
};
|
|
|
|
static void
|
|
gsk_fill_node_finalize (GskRenderNode *node)
|
|
{
|
|
GskFillNode *self = (GskFillNode *) node;
|
|
GskRenderNodeClass *parent_class = g_type_class_peek (g_type_parent (GSK_TYPE_FILL_NODE));
|
|
|
|
gsk_render_node_unref (self->child);
|
|
gsk_path_unref (self->path);
|
|
|
|
parent_class->finalize (node);
|
|
}
|
|
|
|
static void
|
|
gsk_fill_node_draw (GskRenderNode *node,
|
|
cairo_t *cr,
|
|
GdkColorState *ccs)
|
|
{
|
|
GskFillNode *self = (GskFillNode *) node;
|
|
|
|
switch (self->fill_rule)
|
|
{
|
|
case GSK_FILL_RULE_WINDING:
|
|
cairo_set_fill_rule (cr, CAIRO_FILL_RULE_WINDING);
|
|
break;
|
|
case GSK_FILL_RULE_EVEN_ODD:
|
|
cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);
|
|
break;
|
|
default:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
gsk_path_to_cairo (self->path, cr);
|
|
if (gsk_render_node_get_node_type (self->child) == GSK_COLOR_NODE &&
|
|
gsk_rect_contains_rect (&self->child->bounds, &node->bounds))
|
|
{
|
|
gdk_cairo_set_source_rgba_ccs (cr, ccs, gsk_color_node_get_color (self->child));
|
|
cairo_fill (cr);
|
|
}
|
|
else
|
|
{
|
|
cairo_clip (cr);
|
|
gsk_render_node_draw_ccs (self->child, cr, ccs);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gsk_fill_node_diff (GskRenderNode *node1,
|
|
GskRenderNode *node2,
|
|
GskDiffData *data)
|
|
{
|
|
GskFillNode *self1 = (GskFillNode *) node1;
|
|
GskFillNode *self2 = (GskFillNode *) node2;
|
|
|
|
if (self1->path == self2->path)
|
|
{
|
|
cairo_region_t *sub;
|
|
cairo_rectangle_int_t clip_rect;
|
|
|
|
sub = cairo_region_create();
|
|
gsk_render_node_diff (self1->child, self2->child, &(GskDiffData) { sub, data->surface });
|
|
gsk_rect_to_cairo_grow (&node1->bounds, &clip_rect);
|
|
cairo_region_intersect_rectangle (sub, &clip_rect);
|
|
cairo_region_union (data->region, sub);
|
|
cairo_region_destroy (sub);
|
|
}
|
|
else
|
|
{
|
|
gsk_render_node_diff_impossible (node1, node2, data);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gsk_fill_node_class_init (gpointer g_class,
|
|
gpointer class_data)
|
|
{
|
|
GskRenderNodeClass *node_class = g_class;
|
|
|
|
node_class->node_type = GSK_FILL_NODE;
|
|
|
|
node_class->finalize = gsk_fill_node_finalize;
|
|
node_class->draw = gsk_fill_node_draw;
|
|
node_class->diff = gsk_fill_node_diff;
|
|
}
|
|
|
|
/**
|
|
* gsk_fill_node_new:
|
|
* @child: The node to fill the area with
|
|
* @path: The path describing the area to fill
|
|
* @fill_rule: The fill rule to use
|
|
*
|
|
* Creates a `GskRenderNode` that will fill the @child in the area
|
|
* given by @path and @fill_rule.
|
|
*
|
|
* Returns: (transfer none) (type GskFillNode): A new `GskRenderNode`
|
|
*
|
|
* Since: 4.14
|
|
*/
|
|
GskRenderNode *
|
|
gsk_fill_node_new (GskRenderNode *child,
|
|
GskPath *path,
|
|
GskFillRule fill_rule)
|
|
{
|
|
GskFillNode *self;
|
|
GskRenderNode *node;
|
|
graphene_rect_t path_bounds;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE (child), NULL);
|
|
g_return_val_if_fail (path != NULL, NULL);
|
|
|
|
self = gsk_render_node_alloc (GSK_FILL_NODE);
|
|
node = (GskRenderNode *) self;
|
|
node->offscreen_for_opacity = child->offscreen_for_opacity;
|
|
node->preferred_depth = gsk_render_node_get_preferred_depth (child);
|
|
|
|
self->child = gsk_render_node_ref (child);
|
|
self->path = gsk_path_ref (path);
|
|
self->fill_rule = fill_rule;
|
|
|
|
if (gsk_path_get_bounds (path, &path_bounds))
|
|
gsk_rect_intersection (&path_bounds, &child->bounds, &node->bounds);
|
|
else
|
|
gsk_rect_init (&node->bounds, 0.f, 0.f, 0.f, 0.f);
|
|
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* gsk_fill_node_get_child:
|
|
* @node: (type GskFillNode): a fill `GskRenderNode`
|
|
*
|
|
* Gets the child node that is getting drawn by the given @node.
|
|
*
|
|
* Returns: (transfer none): The child that is getting drawn
|
|
*
|
|
* Since: 4.14
|
|
*/
|
|
GskRenderNode *
|
|
gsk_fill_node_get_child (const GskRenderNode *node)
|
|
{
|
|
const GskFillNode *self = (const GskFillNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_FILL_NODE), NULL);
|
|
|
|
return self->child;
|
|
}
|
|
|
|
/**
|
|
* gsk_fill_node_get_path:
|
|
* @node: (type GskFillNode): a fill `GskRenderNode`
|
|
*
|
|
* Retrieves the path used to describe the area filled with the contents of
|
|
* the @node.
|
|
*
|
|
* Returns: (transfer none): a `GskPath`
|
|
*
|
|
* Since: 4.14
|
|
*/
|
|
GskPath *
|
|
gsk_fill_node_get_path (const GskRenderNode *node)
|
|
{
|
|
const GskFillNode *self = (const GskFillNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_FILL_NODE), NULL);
|
|
|
|
return self->path;
|
|
}
|
|
|
|
/**
|
|
* gsk_fill_node_get_fill_rule:
|
|
* @node: (type GskFillNode): a fill `GskRenderNode`
|
|
*
|
|
* Retrieves the fill rule used to determine how the path is filled.
|
|
*
|
|
* Returns: a `GskFillRule`
|
|
*
|
|
* Since: 4.14
|
|
*/
|
|
GskFillRule
|
|
gsk_fill_node_get_fill_rule (const GskRenderNode *node)
|
|
{
|
|
const GskFillNode *self = (const GskFillNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_FILL_NODE), GSK_FILL_RULE_WINDING);
|
|
|
|
return self->fill_rule;
|
|
}
|
|
|
|
/* }}} */
|
|
/* {{{ GSK_STROKE_NODE */
|
|
|
|
/**
|
|
* GskStrokeNode:
|
|
*
|
|
* A render node that will fill the area determined by stroking the the given
|
|
* [struct@Gsk.Path] using the [struct@Gsk.Stroke] attributes.
|
|
*
|
|
* Since: 4.14
|
|
*/
|
|
|
|
struct _GskStrokeNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
GskRenderNode *child;
|
|
GskPath *path;
|
|
GskStroke stroke;
|
|
};
|
|
|
|
static void
|
|
gsk_stroke_node_finalize (GskRenderNode *node)
|
|
{
|
|
GskStrokeNode *self = (GskStrokeNode *) node;
|
|
GskRenderNodeClass *parent_class = g_type_class_peek (g_type_parent (GSK_TYPE_STROKE_NODE));
|
|
|
|
gsk_render_node_unref (self->child);
|
|
gsk_path_unref (self->path);
|
|
gsk_stroke_clear (&self->stroke);
|
|
|
|
parent_class->finalize (node);
|
|
}
|
|
|
|
static void
|
|
gsk_stroke_node_draw (GskRenderNode *node,
|
|
cairo_t *cr,
|
|
GdkColorState *ccs)
|
|
{
|
|
GskStrokeNode *self = (GskStrokeNode *) node;
|
|
|
|
if (gsk_render_node_get_node_type (self->child) == GSK_COLOR_NODE &&
|
|
gsk_rect_contains_rect (&self->child->bounds, &node->bounds))
|
|
{
|
|
gdk_cairo_set_source_rgba_ccs (cr, ccs, gsk_color_node_get_color (self->child));
|
|
}
|
|
else
|
|
{
|
|
gsk_cairo_rectangle_pixel_aligned (cr, &self->child->bounds);
|
|
cairo_clip (cr);
|
|
if (has_empty_clip (cr))
|
|
return;
|
|
|
|
cairo_push_group (cr);
|
|
gsk_render_node_draw_ccs (self->child, cr, ccs);
|
|
cairo_pop_group_to_source (cr);
|
|
}
|
|
|
|
gsk_stroke_to_cairo (&self->stroke, cr);
|
|
|
|
gsk_path_to_cairo (self->path, cr);
|
|
cairo_stroke (cr);
|
|
}
|
|
|
|
static void
|
|
gsk_stroke_node_diff (GskRenderNode *node1,
|
|
GskRenderNode *node2,
|
|
GskDiffData *data)
|
|
{
|
|
GskStrokeNode *self1 = (GskStrokeNode *) node1;
|
|
GskStrokeNode *self2 = (GskStrokeNode *) node2;
|
|
|
|
if (self1->path == self2->path &&
|
|
gsk_stroke_equal (&self1->stroke, &self2->stroke))
|
|
{
|
|
cairo_region_t *sub;
|
|
cairo_rectangle_int_t clip_rect;
|
|
|
|
sub = cairo_region_create();
|
|
gsk_render_node_diff (self1->child, self2->child, &(GskDiffData) { sub, data->surface });
|
|
gsk_rect_to_cairo_grow (&node1->bounds, &clip_rect);
|
|
cairo_region_intersect_rectangle (sub, &clip_rect);
|
|
cairo_region_union (data->region, sub);
|
|
cairo_region_destroy (sub);
|
|
}
|
|
else
|
|
{
|
|
gsk_render_node_diff_impossible (node1, node2, data);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gsk_stroke_node_class_init (gpointer g_class,
|
|
gpointer class_data)
|
|
{
|
|
GskRenderNodeClass *node_class = g_class;
|
|
|
|
node_class->node_type = GSK_STROKE_NODE;
|
|
|
|
node_class->finalize = gsk_stroke_node_finalize;
|
|
node_class->draw = gsk_stroke_node_draw;
|
|
node_class->diff = gsk_stroke_node_diff;
|
|
}
|
|
|
|
/**
|
|
* gsk_stroke_node_new:
|
|
* @child: The node to stroke the area with
|
|
* @path: (transfer none): The path describing the area to stroke
|
|
* @stroke: (transfer none): The stroke attributes to use
|
|
*
|
|
* Creates a #GskRenderNode that will fill the outline generated by stroking
|
|
* the given @path using the attributes defined in @stroke.
|
|
*
|
|
* The area is filled with @child.
|
|
*
|
|
* Returns: (transfer none) (type GskStrokeNode): A new #GskRenderNode
|
|
*
|
|
* Since: 4.14
|
|
*/
|
|
GskRenderNode *
|
|
gsk_stroke_node_new (GskRenderNode *child,
|
|
GskPath *path,
|
|
const GskStroke *stroke)
|
|
{
|
|
GskStrokeNode *self;
|
|
GskRenderNode *node;
|
|
graphene_rect_t stroke_bounds;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE (child), NULL);
|
|
g_return_val_if_fail (path != NULL, NULL);
|
|
g_return_val_if_fail (stroke != NULL, NULL);
|
|
|
|
self = gsk_render_node_alloc (GSK_STROKE_NODE);
|
|
node = (GskRenderNode *) self;
|
|
node->offscreen_for_opacity = child->offscreen_for_opacity;
|
|
node->preferred_depth = gsk_render_node_get_preferred_depth (child);
|
|
|
|
self->child = gsk_render_node_ref (child);
|
|
self->path = gsk_path_ref (path);
|
|
self->stroke = GSK_STROKE_INIT_COPY (stroke);
|
|
|
|
if (gsk_path_get_stroke_bounds (self->path, &self->stroke, &stroke_bounds))
|
|
gsk_rect_intersection (&stroke_bounds, &child->bounds, &node->bounds);
|
|
else
|
|
gsk_rect_init (&node->bounds, 0.f, 0.f, 0.f, 0.f);
|
|
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* gsk_stroke_node_get_child:
|
|
* @node: (type GskStrokeNode): a stroke #GskRenderNode
|
|
*
|
|
* Gets the child node that is getting drawn by the given @node.
|
|
*
|
|
* Returns: (transfer none): The child that is getting drawn
|
|
*
|
|
* Since: 4.14
|
|
*/
|
|
GskRenderNode *
|
|
gsk_stroke_node_get_child (const GskRenderNode *node)
|
|
{
|
|
const GskStrokeNode *self = (const GskStrokeNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_STROKE_NODE), NULL);
|
|
|
|
return self->child;
|
|
}
|
|
|
|
/**
|
|
* gsk_stroke_node_get_path:
|
|
* @node: (type GskStrokeNode): a stroke #GskRenderNode
|
|
*
|
|
* Retrieves the path that will be stroked with the contents of
|
|
* the @node.
|
|
*
|
|
* Returns: (transfer none): a #GskPath
|
|
*
|
|
* Since: 4.14
|
|
*/
|
|
GskPath *
|
|
gsk_stroke_node_get_path (const GskRenderNode *node)
|
|
{
|
|
const GskStrokeNode *self = (const GskStrokeNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_STROKE_NODE), NULL);
|
|
|
|
return self->path;
|
|
}
|
|
|
|
/**
|
|
* gsk_stroke_node_get_stroke:
|
|
* @node: (type GskStrokeNode): a stroke #GskRenderNode
|
|
*
|
|
* Retrieves the stroke attributes used in this @node.
|
|
*
|
|
* Returns: a #GskStroke
|
|
*
|
|
* Since: 4.14
|
|
*/
|
|
const GskStroke *
|
|
gsk_stroke_node_get_stroke (const GskRenderNode *node)
|
|
{
|
|
const GskStrokeNode *self = (const GskStrokeNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_STROKE_NODE), NULL);
|
|
|
|
return &self->stroke;
|
|
}
|
|
|
|
/* }}} */
|
|
/* {{{ GSK_SHADOW_NODE */
|
|
|
|
/**
|
|
* GskShadowNode:
|
|
*
|
|
* A render node drawing one or more shadows behind its single child node.
|
|
*/
|
|
struct _GskShadowNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
GskRenderNode *child;
|
|
|
|
gsize n_shadows;
|
|
GskShadow *shadows;
|
|
};
|
|
|
|
static void
|
|
gsk_shadow_node_finalize (GskRenderNode *node)
|
|
{
|
|
GskShadowNode *self = (GskShadowNode *) node;
|
|
GskRenderNodeClass *parent_class = g_type_class_peek (g_type_parent (GSK_TYPE_SHADOW_NODE));
|
|
|
|
gsk_render_node_unref (self->child);
|
|
g_free (self->shadows);
|
|
|
|
parent_class->finalize (node);
|
|
}
|
|
|
|
static void
|
|
gsk_shadow_node_draw (GskRenderNode *node,
|
|
cairo_t *cr,
|
|
GdkColorState *ccs)
|
|
{
|
|
GskShadowNode *self = (GskShadowNode *) node;
|
|
gsize i;
|
|
|
|
/* clip so the blur area stays small */
|
|
gsk_cairo_rectangle_pixel_aligned (cr, &node->bounds);
|
|
cairo_clip (cr);
|
|
if (has_empty_clip (cr))
|
|
return;
|
|
|
|
for (i = 0; i < self->n_shadows; i++)
|
|
{
|
|
GskShadow *shadow = &self->shadows[i];
|
|
cairo_pattern_t *pattern;
|
|
|
|
/* We don't need to draw invisible shadows */
|
|
if (gdk_rgba_is_clear (&shadow->color))
|
|
continue;
|
|
|
|
cairo_save (cr);
|
|
cr = gsk_cairo_blur_start_drawing (cr, 0.5 * shadow->radius, GSK_BLUR_X | GSK_BLUR_Y);
|
|
|
|
cairo_save (cr);
|
|
cairo_translate (cr, shadow->dx, shadow->dy);
|
|
cairo_push_group (cr);
|
|
gsk_render_node_draw_ccs (self->child, cr, ccs);
|
|
pattern = cairo_pop_group (cr);
|
|
cairo_reset_clip (cr);
|
|
gdk_cairo_set_source_rgba_ccs (cr, ccs, &shadow->color);
|
|
cairo_mask (cr, pattern);
|
|
cairo_pattern_destroy (pattern);
|
|
cairo_restore (cr);
|
|
|
|
cr = gsk_cairo_blur_finish_drawing (cr, ccs, 0.5 * shadow->radius, &shadow->color, GSK_BLUR_X | GSK_BLUR_Y);
|
|
cairo_restore (cr);
|
|
}
|
|
|
|
gsk_render_node_draw_ccs (self->child, cr, ccs);
|
|
}
|
|
|
|
static void
|
|
gsk_shadow_node_diff (GskRenderNode *node1,
|
|
GskRenderNode *node2,
|
|
GskDiffData *data)
|
|
{
|
|
GskShadowNode *self1 = (GskShadowNode *) node1;
|
|
GskShadowNode *self2 = (GskShadowNode *) node2;
|
|
int top = 0, right = 0, bottom = 0, left = 0;
|
|
cairo_region_t *sub;
|
|
cairo_rectangle_int_t rect;
|
|
gsize i, n;
|
|
|
|
if (self1->n_shadows != self2->n_shadows)
|
|
{
|
|
gsk_render_node_diff_impossible (node1, node2, data);
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < self1->n_shadows; i++)
|
|
{
|
|
GskShadow *shadow1 = &self1->shadows[i];
|
|
GskShadow *shadow2 = &self2->shadows[i];
|
|
float clip_radius;
|
|
|
|
if (!gdk_rgba_equal (&shadow1->color, &shadow2->color) ||
|
|
shadow1->dx != shadow2->dx ||
|
|
shadow1->dy != shadow2->dy ||
|
|
shadow1->radius != shadow2->radius)
|
|
{
|
|
gsk_render_node_diff_impossible (node1, node2, data);
|
|
return;
|
|
}
|
|
|
|
clip_radius = gsk_cairo_blur_compute_pixels (shadow1->radius / 2.0);
|
|
top = MAX (top, ceil (clip_radius - shadow1->dy));
|
|
right = MAX (right, ceil (clip_radius + shadow1->dx));
|
|
bottom = MAX (bottom, ceil (clip_radius + shadow1->dy));
|
|
left = MAX (left, ceil (clip_radius - shadow1->dx));
|
|
}
|
|
|
|
sub = cairo_region_create ();
|
|
gsk_render_node_diff (self1->child, self2->child, &(GskDiffData) { sub, data->surface });
|
|
|
|
n = cairo_region_num_rectangles (sub);
|
|
for (i = 0; i < n; i++)
|
|
{
|
|
cairo_region_get_rectangle (sub, i, &rect);
|
|
rect.x -= left;
|
|
rect.y -= top;
|
|
rect.width += left + right;
|
|
rect.height += top + bottom;
|
|
cairo_region_union_rectangle (data->region, &rect);
|
|
}
|
|
cairo_region_destroy (sub);
|
|
}
|
|
|
|
static void
|
|
gsk_shadow_node_get_bounds (GskShadowNode *self,
|
|
graphene_rect_t *bounds)
|
|
{
|
|
float top = 0, right = 0, bottom = 0, left = 0;
|
|
gsize i;
|
|
|
|
gsk_rect_init_from_rect (bounds, &self->child->bounds);
|
|
|
|
for (i = 0; i < self->n_shadows; i++)
|
|
{
|
|
float clip_radius = gsk_cairo_blur_compute_pixels (self->shadows[i].radius / 2.0);
|
|
top = MAX (top, clip_radius - self->shadows[i].dy);
|
|
right = MAX (right, clip_radius + self->shadows[i].dx);
|
|
bottom = MAX (bottom, clip_radius + self->shadows[i].dy);
|
|
left = MAX (left, clip_radius - self->shadows[i].dx);
|
|
}
|
|
|
|
bounds->origin.x -= left;
|
|
bounds->origin.y -= top;
|
|
bounds->size.width += left + right;
|
|
bounds->size.height += top + bottom;
|
|
}
|
|
|
|
static void
|
|
gsk_shadow_node_class_init (gpointer g_class,
|
|
gpointer class_data)
|
|
{
|
|
GskRenderNodeClass *node_class = g_class;
|
|
|
|
node_class->node_type = GSK_SHADOW_NODE;
|
|
|
|
node_class->finalize = gsk_shadow_node_finalize;
|
|
node_class->draw = gsk_shadow_node_draw;
|
|
node_class->diff = gsk_shadow_node_diff;
|
|
}
|
|
|
|
/**
|
|
* gsk_shadow_node_new:
|
|
* @child: The node to draw
|
|
* @shadows: (array length=n_shadows): The shadows to apply
|
|
* @n_shadows: number of entries in the @shadows array
|
|
*
|
|
* Creates a `GskRenderNode` that will draw a @child with the given
|
|
* @shadows below it.
|
|
*
|
|
* Returns: (transfer full) (type GskShadowNode): A new `GskRenderNode`
|
|
*/
|
|
GskRenderNode *
|
|
gsk_shadow_node_new (GskRenderNode *child,
|
|
const GskShadow *shadows,
|
|
gsize n_shadows)
|
|
{
|
|
GskShadowNode *self;
|
|
GskRenderNode *node;
|
|
gsize i;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE (child), NULL);
|
|
g_return_val_if_fail (shadows != NULL, NULL);
|
|
g_return_val_if_fail (n_shadows > 0, NULL);
|
|
|
|
self = gsk_render_node_alloc (GSK_SHADOW_NODE);
|
|
node = (GskRenderNode *) self;
|
|
node->offscreen_for_opacity = TRUE;
|
|
|
|
self->child = gsk_render_node_ref (child);
|
|
self->n_shadows = n_shadows;
|
|
self->shadows = g_malloc_n (n_shadows, sizeof (GskShadow));
|
|
memcpy (self->shadows, shadows, n_shadows * sizeof (GskShadow));
|
|
|
|
gsk_shadow_node_get_bounds (self, &node->bounds);
|
|
|
|
node->preferred_depth = gsk_render_node_get_preferred_depth (child);
|
|
for (i = 0; i < n_shadows; i++)
|
|
{
|
|
node->preferred_depth = gdk_memory_depth_merge (node->preferred_depth,
|
|
my_color_get_depth (&shadows->color));
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* gsk_shadow_node_get_child:
|
|
* @node: (type GskShadowNode): a shadow `GskRenderNode`
|
|
*
|
|
* Retrieves the child `GskRenderNode` of the shadow @node.
|
|
*
|
|
* Returns: (transfer none): the child render node
|
|
*/
|
|
GskRenderNode *
|
|
gsk_shadow_node_get_child (const GskRenderNode *node)
|
|
{
|
|
const GskShadowNode *self = (const GskShadowNode *) node;
|
|
|
|
return self->child;
|
|
}
|
|
|
|
/**
|
|
* gsk_shadow_node_get_shadow:
|
|
* @node: (type GskShadowNode): a shadow `GskRenderNode`
|
|
* @i: the given index
|
|
*
|
|
* Retrieves the shadow data at the given index @i.
|
|
*
|
|
* Returns: (transfer none): the shadow data
|
|
*/
|
|
const GskShadow *
|
|
gsk_shadow_node_get_shadow (const GskRenderNode *node,
|
|
gsize i)
|
|
{
|
|
const GskShadowNode *self = (const GskShadowNode *) node;
|
|
|
|
return &self->shadows[i];
|
|
}
|
|
|
|
/**
|
|
* gsk_shadow_node_get_n_shadows:
|
|
* @node: (type GskShadowNode): a shadow `GskRenderNode`
|
|
*
|
|
* Retrieves the number of shadows in the @node.
|
|
*
|
|
* Returns: the number of shadows.
|
|
*/
|
|
gsize
|
|
gsk_shadow_node_get_n_shadows (const GskRenderNode *node)
|
|
{
|
|
const GskShadowNode *self = (const GskShadowNode *) node;
|
|
|
|
return self->n_shadows;
|
|
}
|
|
|
|
/* }}} */
|
|
/* {{{ GSK_BLEND_NODE */
|
|
|
|
/**
|
|
* GskBlendNode:
|
|
*
|
|
* A render node applying a blending function between its two child nodes.
|
|
*/
|
|
struct _GskBlendNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
GskRenderNode *bottom;
|
|
GskRenderNode *top;
|
|
GskBlendMode blend_mode;
|
|
};
|
|
|
|
static cairo_operator_t
|
|
gsk_blend_mode_to_cairo_operator (GskBlendMode blend_mode)
|
|
{
|
|
switch (blend_mode)
|
|
{
|
|
default:
|
|
g_assert_not_reached ();
|
|
case GSK_BLEND_MODE_DEFAULT:
|
|
return CAIRO_OPERATOR_OVER;
|
|
case GSK_BLEND_MODE_MULTIPLY:
|
|
return CAIRO_OPERATOR_MULTIPLY;
|
|
case GSK_BLEND_MODE_SCREEN:
|
|
return CAIRO_OPERATOR_SCREEN;
|
|
case GSK_BLEND_MODE_OVERLAY:
|
|
return CAIRO_OPERATOR_OVERLAY;
|
|
case GSK_BLEND_MODE_DARKEN:
|
|
return CAIRO_OPERATOR_DARKEN;
|
|
case GSK_BLEND_MODE_LIGHTEN:
|
|
return CAIRO_OPERATOR_LIGHTEN;
|
|
case GSK_BLEND_MODE_COLOR_DODGE:
|
|
return CAIRO_OPERATOR_COLOR_DODGE;
|
|
case GSK_BLEND_MODE_COLOR_BURN:
|
|
return CAIRO_OPERATOR_COLOR_BURN;
|
|
case GSK_BLEND_MODE_HARD_LIGHT:
|
|
return CAIRO_OPERATOR_HARD_LIGHT;
|
|
case GSK_BLEND_MODE_SOFT_LIGHT:
|
|
return CAIRO_OPERATOR_SOFT_LIGHT;
|
|
case GSK_BLEND_MODE_DIFFERENCE:
|
|
return CAIRO_OPERATOR_DIFFERENCE;
|
|
case GSK_BLEND_MODE_EXCLUSION:
|
|
return CAIRO_OPERATOR_EXCLUSION;
|
|
case GSK_BLEND_MODE_COLOR:
|
|
return CAIRO_OPERATOR_HSL_COLOR;
|
|
case GSK_BLEND_MODE_HUE:
|
|
return CAIRO_OPERATOR_HSL_HUE;
|
|
case GSK_BLEND_MODE_SATURATION:
|
|
return CAIRO_OPERATOR_HSL_SATURATION;
|
|
case GSK_BLEND_MODE_LUMINOSITY:
|
|
return CAIRO_OPERATOR_HSL_LUMINOSITY;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gsk_blend_node_finalize (GskRenderNode *node)
|
|
{
|
|
GskBlendNode *self = (GskBlendNode *) node;
|
|
GskRenderNodeClass *parent_class = g_type_class_peek (g_type_parent (GSK_TYPE_BLEND_NODE));
|
|
|
|
gsk_render_node_unref (self->bottom);
|
|
gsk_render_node_unref (self->top);
|
|
|
|
parent_class->finalize (node);
|
|
}
|
|
|
|
static void
|
|
gsk_blend_node_draw (GskRenderNode *node,
|
|
cairo_t *cr,
|
|
GdkColorState *ccs)
|
|
{
|
|
GskBlendNode *self = (GskBlendNode *) node;
|
|
|
|
if (has_empty_clip (cr))
|
|
return;
|
|
|
|
if (!gdk_color_state_equal (ccs, GDK_COLOR_STATE_SRGB))
|
|
g_warning ("blend node in non-srgb colorstate isn't implemented yet.");
|
|
|
|
cairo_push_group (cr);
|
|
gsk_render_node_draw_ccs (self->bottom, cr, ccs);
|
|
|
|
cairo_push_group (cr);
|
|
gsk_render_node_draw_ccs (self->top, cr, ccs);
|
|
|
|
cairo_pop_group_to_source (cr);
|
|
cairo_set_operator (cr, gsk_blend_mode_to_cairo_operator (self->blend_mode));
|
|
cairo_paint (cr);
|
|
|
|
cairo_pop_group_to_source (cr); /* resets operator */
|
|
cairo_paint (cr);
|
|
}
|
|
|
|
static void
|
|
gsk_blend_node_diff (GskRenderNode *node1,
|
|
GskRenderNode *node2,
|
|
GskDiffData *data)
|
|
{
|
|
GskBlendNode *self1 = (GskBlendNode *) node1;
|
|
GskBlendNode *self2 = (GskBlendNode *) node2;
|
|
|
|
if (self1->blend_mode == self2->blend_mode)
|
|
{
|
|
gsk_render_node_diff (self1->top, self2->top, data);
|
|
gsk_render_node_diff (self1->bottom, self2->bottom, data);
|
|
}
|
|
else
|
|
{
|
|
gsk_render_node_diff_impossible (node1, node2, data);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gsk_blend_node_class_init (gpointer g_class,
|
|
gpointer class_data)
|
|
{
|
|
GskRenderNodeClass *node_class = g_class;
|
|
|
|
node_class->node_type = GSK_BLEND_NODE;
|
|
|
|
node_class->finalize = gsk_blend_node_finalize;
|
|
node_class->draw = gsk_blend_node_draw;
|
|
node_class->diff = gsk_blend_node_diff;
|
|
}
|
|
|
|
/**
|
|
* gsk_blend_node_new:
|
|
* @bottom: The bottom node to be drawn
|
|
* @top: The node to be blended onto the @bottom node
|
|
* @blend_mode: The blend mode to use
|
|
*
|
|
* Creates a `GskRenderNode` that will use @blend_mode to blend the @top
|
|
* node onto the @bottom node.
|
|
*
|
|
* Returns: (transfer full) (type GskBlendNode): A new `GskRenderNode`
|
|
*/
|
|
GskRenderNode *
|
|
gsk_blend_node_new (GskRenderNode *bottom,
|
|
GskRenderNode *top,
|
|
GskBlendMode blend_mode)
|
|
{
|
|
GskBlendNode *self;
|
|
GskRenderNode *node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE (bottom), NULL);
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE (top), NULL);
|
|
|
|
self = gsk_render_node_alloc (GSK_BLEND_NODE);
|
|
node = (GskRenderNode *) self;
|
|
node->offscreen_for_opacity = TRUE;
|
|
|
|
self->bottom = gsk_render_node_ref (bottom);
|
|
self->top = gsk_render_node_ref (top);
|
|
self->blend_mode = blend_mode;
|
|
|
|
graphene_rect_union (&bottom->bounds, &top->bounds, &node->bounds);
|
|
|
|
node->preferred_depth = gdk_memory_depth_merge (gsk_render_node_get_preferred_depth (bottom),
|
|
gsk_render_node_get_preferred_depth (top));
|
|
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* gsk_blend_node_get_bottom_child:
|
|
* @node: (type GskBlendNode): a blending `GskRenderNode`
|
|
*
|
|
* Retrieves the bottom `GskRenderNode` child of the @node.
|
|
*
|
|
* Returns: (transfer none): the bottom child node
|
|
*/
|
|
GskRenderNode *
|
|
gsk_blend_node_get_bottom_child (const GskRenderNode *node)
|
|
{
|
|
const GskBlendNode *self = (const GskBlendNode *) node;
|
|
|
|
return self->bottom;
|
|
}
|
|
|
|
/**
|
|
* gsk_blend_node_get_top_child:
|
|
* @node: (type GskBlendNode): a blending `GskRenderNode`
|
|
*
|
|
* Retrieves the top `GskRenderNode` child of the @node.
|
|
*
|
|
* Returns: (transfer none): the top child node
|
|
*/
|
|
GskRenderNode *
|
|
gsk_blend_node_get_top_child (const GskRenderNode *node)
|
|
{
|
|
const GskBlendNode *self = (const GskBlendNode *) node;
|
|
|
|
return self->top;
|
|
}
|
|
|
|
/**
|
|
* gsk_blend_node_get_blend_mode:
|
|
* @node: (type GskBlendNode): a blending `GskRenderNode`
|
|
*
|
|
* Retrieves the blend mode used by @node.
|
|
*
|
|
* Returns: the blend mode
|
|
*/
|
|
GskBlendMode
|
|
gsk_blend_node_get_blend_mode (const GskRenderNode *node)
|
|
{
|
|
const GskBlendNode *self = (const GskBlendNode *) node;
|
|
|
|
return self->blend_mode;
|
|
}
|
|
|
|
/* }}} */
|
|
/* {{{ GSK_CROSS_FADE_NODE */
|
|
|
|
/**
|
|
* GskCrossFadeNode:
|
|
*
|
|
* A render node cross fading between two child nodes.
|
|
*/
|
|
struct _GskCrossFadeNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
GskRenderNode *start;
|
|
GskRenderNode *end;
|
|
float progress;
|
|
};
|
|
|
|
static void
|
|
gsk_cross_fade_node_finalize (GskRenderNode *node)
|
|
{
|
|
GskCrossFadeNode *self = (GskCrossFadeNode *) node;
|
|
GskRenderNodeClass *parent_class = g_type_class_peek (g_type_parent (GSK_TYPE_CROSS_FADE_NODE));
|
|
|
|
gsk_render_node_unref (self->start);
|
|
gsk_render_node_unref (self->end);
|
|
|
|
parent_class->finalize (node);
|
|
}
|
|
|
|
static void
|
|
gsk_cross_fade_node_draw (GskRenderNode *node,
|
|
cairo_t *cr,
|
|
GdkColorState *ccs)
|
|
{
|
|
GskCrossFadeNode *self = (GskCrossFadeNode *) node;
|
|
|
|
if (has_empty_clip (cr))
|
|
return;
|
|
|
|
cairo_push_group_with_content (cr, CAIRO_CONTENT_COLOR_ALPHA);
|
|
gsk_render_node_draw_ccs (self->start, cr, ccs);
|
|
|
|
cairo_push_group_with_content (cr, CAIRO_CONTENT_COLOR_ALPHA);
|
|
gsk_render_node_draw_ccs (self->end, cr, ccs);
|
|
|
|
cairo_pop_group_to_source (cr);
|
|
cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
|
|
cairo_paint_with_alpha (cr, self->progress);
|
|
|
|
cairo_pop_group_to_source (cr); /* resets operator */
|
|
cairo_paint (cr);
|
|
}
|
|
|
|
static void
|
|
gsk_cross_fade_node_diff (GskRenderNode *node1,
|
|
GskRenderNode *node2,
|
|
GskDiffData *data)
|
|
{
|
|
GskCrossFadeNode *self1 = (GskCrossFadeNode *) node1;
|
|
GskCrossFadeNode *self2 = (GskCrossFadeNode *) node2;
|
|
|
|
if (self1->progress == self2->progress)
|
|
{
|
|
gsk_render_node_diff (self1->start, self2->start, data);
|
|
gsk_render_node_diff (self1->end, self2->end, data);
|
|
return;
|
|
}
|
|
|
|
gsk_render_node_diff_impossible (node1, node2, data);
|
|
}
|
|
|
|
static gboolean
|
|
gsk_cross_fade_node_get_opaque_rect (GskRenderNode *node,
|
|
graphene_rect_t *opaque)
|
|
{
|
|
GskCrossFadeNode *self = (GskCrossFadeNode *) node;
|
|
graphene_rect_t start_opaque, end_opaque;
|
|
|
|
if (!gsk_render_node_get_opaque_rect (self->start, &start_opaque) ||
|
|
!gsk_render_node_get_opaque_rect (self->end, &end_opaque))
|
|
return FALSE;
|
|
|
|
return graphene_rect_intersection (&start_opaque, &end_opaque, opaque);
|
|
}
|
|
|
|
static void
|
|
gsk_cross_fade_node_class_init (gpointer g_class,
|
|
gpointer class_data)
|
|
{
|
|
GskRenderNodeClass *node_class = g_class;
|
|
|
|
node_class->node_type = GSK_CROSS_FADE_NODE;
|
|
|
|
node_class->finalize = gsk_cross_fade_node_finalize;
|
|
node_class->draw = gsk_cross_fade_node_draw;
|
|
node_class->diff = gsk_cross_fade_node_diff;
|
|
node_class->get_opaque_rect = gsk_cross_fade_node_get_opaque_rect;
|
|
}
|
|
|
|
/**
|
|
* gsk_cross_fade_node_new:
|
|
* @start: The start node to be drawn
|
|
* @end: The node to be cross_fadeed onto the @start node
|
|
* @progress: How far the fade has progressed from start to end. The value will
|
|
* be clamped to the range [0 ... 1]
|
|
*
|
|
* Creates a `GskRenderNode` that will do a cross-fade between @start and @end.
|
|
*
|
|
* Returns: (transfer full) (type GskCrossFadeNode): A new `GskRenderNode`
|
|
*/
|
|
GskRenderNode *
|
|
gsk_cross_fade_node_new (GskRenderNode *start,
|
|
GskRenderNode *end,
|
|
float progress)
|
|
{
|
|
GskCrossFadeNode *self;
|
|
GskRenderNode *node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE (start), NULL);
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE (end), NULL);
|
|
|
|
self = gsk_render_node_alloc (GSK_CROSS_FADE_NODE);
|
|
node = (GskRenderNode *) self;
|
|
node->offscreen_for_opacity = TRUE;
|
|
node->fully_opaque = start->fully_opaque && end->fully_opaque &&
|
|
gsk_rect_equal (&start->bounds, &end->bounds);
|
|
|
|
self->start = gsk_render_node_ref (start);
|
|
self->end = gsk_render_node_ref (end);
|
|
self->progress = CLAMP (progress, 0.0, 1.0);
|
|
|
|
graphene_rect_union (&start->bounds, &end->bounds, &node->bounds);
|
|
|
|
node->preferred_depth = gdk_memory_depth_merge (gsk_render_node_get_preferred_depth (start),
|
|
gsk_render_node_get_preferred_depth (end));
|
|
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* gsk_cross_fade_node_get_start_child:
|
|
* @node: (type GskCrossFadeNode): a cross-fading `GskRenderNode`
|
|
*
|
|
* Retrieves the child `GskRenderNode` at the beginning of the cross-fade.
|
|
*
|
|
* Returns: (transfer none): a `GskRenderNode`
|
|
*/
|
|
GskRenderNode *
|
|
gsk_cross_fade_node_get_start_child (const GskRenderNode *node)
|
|
{
|
|
const GskCrossFadeNode *self = (const GskCrossFadeNode *) node;
|
|
|
|
return self->start;
|
|
}
|
|
|
|
/**
|
|
* gsk_cross_fade_node_get_end_child:
|
|
* @node: (type GskCrossFadeNode): a cross-fading `GskRenderNode`
|
|
*
|
|
* Retrieves the child `GskRenderNode` at the end of the cross-fade.
|
|
*
|
|
* Returns: (transfer none): a `GskRenderNode`
|
|
*/
|
|
GskRenderNode *
|
|
gsk_cross_fade_node_get_end_child (const GskRenderNode *node)
|
|
{
|
|
const GskCrossFadeNode *self = (const GskCrossFadeNode *) node;
|
|
|
|
return self->end;
|
|
}
|
|
|
|
/**
|
|
* gsk_cross_fade_node_get_progress:
|
|
* @node: (type GskCrossFadeNode): a cross-fading `GskRenderNode`
|
|
*
|
|
* Retrieves the progress value of the cross fade.
|
|
*
|
|
* Returns: the progress value, between 0 and 1
|
|
*/
|
|
float
|
|
gsk_cross_fade_node_get_progress (const GskRenderNode *node)
|
|
{
|
|
const GskCrossFadeNode *self = (const GskCrossFadeNode *) node;
|
|
|
|
return self->progress;
|
|
}
|
|
|
|
/* }}} */
|
|
/* {{{ GSK_TEXT_NODE */
|
|
|
|
/**
|
|
* GskTextNode:
|
|
*
|
|
* A render node drawing a set of glyphs.
|
|
*/
|
|
struct _GskTextNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
PangoFontMap *fontmap;
|
|
PangoFont *font;
|
|
gboolean has_color_glyphs;
|
|
|
|
GdkRGBA color;
|
|
graphene_point_t offset;
|
|
|
|
guint num_glyphs;
|
|
PangoGlyphInfo *glyphs;
|
|
};
|
|
|
|
static void
|
|
gsk_text_node_finalize (GskRenderNode *node)
|
|
{
|
|
GskTextNode *self = (GskTextNode *) node;
|
|
GskRenderNodeClass *parent_class = g_type_class_peek (g_type_parent (GSK_TYPE_TEXT_NODE));
|
|
|
|
g_object_unref (self->font);
|
|
g_object_unref (self->fontmap);
|
|
g_free (self->glyphs);
|
|
|
|
parent_class->finalize (node);
|
|
}
|
|
|
|
static void
|
|
gsk_text_node_draw (GskRenderNode *node,
|
|
cairo_t *cr,
|
|
GdkColorState *ccs)
|
|
{
|
|
GskTextNode *self = (GskTextNode *) node;
|
|
PangoGlyphString glyphs;
|
|
|
|
glyphs.num_glyphs = self->num_glyphs;
|
|
glyphs.glyphs = self->glyphs;
|
|
glyphs.log_clusters = NULL;
|
|
|
|
cairo_save (cr);
|
|
|
|
if (!gdk_color_state_equal (ccs, GDK_COLOR_STATE_SRGB) &&
|
|
self->has_color_glyphs)
|
|
{
|
|
g_warning ("whoopsie, color glyphs and we're not in sRGB");
|
|
}
|
|
else
|
|
{
|
|
gdk_cairo_set_source_rgba_ccs (cr, ccs, &self->color);
|
|
cairo_translate (cr, self->offset.x, self->offset.y);
|
|
pango_cairo_show_glyph_string (cr, self->font, &glyphs);
|
|
}
|
|
|
|
cairo_restore (cr);
|
|
}
|
|
|
|
static void
|
|
gsk_text_node_diff (GskRenderNode *node1,
|
|
GskRenderNode *node2,
|
|
GskDiffData *data)
|
|
{
|
|
GskTextNode *self1 = (GskTextNode *) node1;
|
|
GskTextNode *self2 = (GskTextNode *) node2;
|
|
|
|
if (self1->font == self2->font &&
|
|
gdk_rgba_equal (&self1->color, &self2->color) &&
|
|
graphene_point_equal (&self1->offset, &self2->offset) &&
|
|
self1->num_glyphs == self2->num_glyphs)
|
|
{
|
|
guint i;
|
|
|
|
for (i = 0; i < self1->num_glyphs; i++)
|
|
{
|
|
PangoGlyphInfo *info1 = &self1->glyphs[i];
|
|
PangoGlyphInfo *info2 = &self2->glyphs[i];
|
|
|
|
if (info1->glyph == info2->glyph &&
|
|
info1->geometry.width == info2->geometry.width &&
|
|
info1->geometry.x_offset == info2->geometry.x_offset &&
|
|
info1->geometry.y_offset == info2->geometry.y_offset &&
|
|
info1->attr.is_cluster_start == info2->attr.is_cluster_start &&
|
|
info1->attr.is_color == info2->attr.is_color)
|
|
continue;
|
|
|
|
gsk_render_node_diff_impossible (node1, node2, data);
|
|
return;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
gsk_render_node_diff_impossible (node1, node2, data);
|
|
}
|
|
|
|
static void
|
|
gsk_text_node_class_init (gpointer g_class,
|
|
gpointer class_data)
|
|
{
|
|
GskRenderNodeClass *node_class = g_class;
|
|
|
|
node_class->node_type = GSK_TEXT_NODE;
|
|
|
|
node_class->finalize = gsk_text_node_finalize;
|
|
node_class->draw = gsk_text_node_draw;
|
|
node_class->diff = gsk_text_node_diff;
|
|
}
|
|
|
|
static inline float
|
|
pango_units_to_float (int i)
|
|
{
|
|
return (float) i / PANGO_SCALE;
|
|
}
|
|
|
|
/**
|
|
* gsk_text_node_new:
|
|
* @font: the `PangoFont` containing the glyphs
|
|
* @glyphs: the `PangoGlyphString` to render
|
|
* @color: the foreground color to render with
|
|
* @offset: offset of the baseline
|
|
*
|
|
* Creates a render node that renders the given glyphs.
|
|
*
|
|
* Note that @color may not be used if the font contains
|
|
* color glyphs.
|
|
*
|
|
* Returns: (nullable) (transfer full) (type GskTextNode): a new `GskRenderNode`
|
|
*/
|
|
GskRenderNode *
|
|
gsk_text_node_new (PangoFont *font,
|
|
PangoGlyphString *glyphs,
|
|
const GdkRGBA *color,
|
|
const graphene_point_t *offset)
|
|
{
|
|
GskTextNode *self;
|
|
GskRenderNode *node;
|
|
PangoRectangle ink_rect;
|
|
PangoGlyphInfo *glyph_infos;
|
|
int n;
|
|
|
|
pango_glyph_string_extents (glyphs, font, &ink_rect, NULL);
|
|
|
|
/* Don't create nodes with empty bounds */
|
|
if (ink_rect.width == 0 || ink_rect.height == 0)
|
|
return NULL;
|
|
|
|
self = gsk_render_node_alloc (GSK_TEXT_NODE);
|
|
node = (GskRenderNode *) self;
|
|
node->offscreen_for_opacity = FALSE;
|
|
node->preferred_depth = my_color_get_depth (color);
|
|
|
|
self->fontmap = g_object_ref (pango_font_get_font_map (font));
|
|
self->font = g_object_ref (font);
|
|
self->color = *color;
|
|
self->offset = *offset;
|
|
self->has_color_glyphs = FALSE;
|
|
|
|
glyph_infos = g_malloc_n (glyphs->num_glyphs, sizeof (PangoGlyphInfo));
|
|
|
|
n = 0;
|
|
for (int i = 0; i < glyphs->num_glyphs; i++)
|
|
{
|
|
/* skip empty glyphs */
|
|
if (glyphs->glyphs[i].glyph == PANGO_GLYPH_EMPTY)
|
|
continue;
|
|
|
|
glyph_infos[n] = glyphs->glyphs[i];
|
|
|
|
if (glyphs->glyphs[i].attr.is_color)
|
|
self->has_color_glyphs = TRUE;
|
|
|
|
n++;
|
|
}
|
|
|
|
self->glyphs = glyph_infos;
|
|
self->num_glyphs = n;
|
|
|
|
gsk_rect_init (&node->bounds,
|
|
offset->x + pango_units_to_float (ink_rect.x),
|
|
offset->y + pango_units_to_float (ink_rect.y),
|
|
pango_units_to_float (ink_rect.width),
|
|
pango_units_to_float (ink_rect.height));
|
|
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* gsk_text_node_get_color:
|
|
* @node: (type GskTextNode): a text `GskRenderNode`
|
|
*
|
|
* Retrieves the color used by the text @node.
|
|
*
|
|
* Returns: (transfer none): the text color
|
|
*/
|
|
const GdkRGBA *
|
|
gsk_text_node_get_color (const GskRenderNode *node)
|
|
{
|
|
const GskTextNode *self = (const GskTextNode *) node;
|
|
|
|
return &self->color;
|
|
}
|
|
|
|
/**
|
|
* gsk_text_node_get_font:
|
|
* @node: (type GskTextNode): The `GskRenderNode`
|
|
*
|
|
* Returns the font used by the text @node.
|
|
*
|
|
* Returns: (transfer none): the font
|
|
*/
|
|
PangoFont *
|
|
gsk_text_node_get_font (const GskRenderNode *node)
|
|
{
|
|
const GskTextNode *self = (const GskTextNode *) node;
|
|
|
|
return self->font;
|
|
}
|
|
|
|
/**
|
|
* gsk_text_node_has_color_glyphs:
|
|
* @node: (type GskTextNode): a text `GskRenderNode`
|
|
*
|
|
* Checks whether the text @node has color glyphs.
|
|
*
|
|
* Returns: %TRUE if the text node has color glyphs
|
|
*
|
|
* Since: 4.2
|
|
*/
|
|
gboolean
|
|
gsk_text_node_has_color_glyphs (const GskRenderNode *node)
|
|
{
|
|
const GskTextNode *self = (const GskTextNode *) node;
|
|
|
|
return self->has_color_glyphs;
|
|
}
|
|
|
|
/**
|
|
* gsk_text_node_get_num_glyphs:
|
|
* @node: (type GskTextNode): a text `GskRenderNode`
|
|
*
|
|
* Retrieves the number of glyphs in the text node.
|
|
*
|
|
* Returns: the number of glyphs
|
|
*/
|
|
guint
|
|
gsk_text_node_get_num_glyphs (const GskRenderNode *node)
|
|
{
|
|
const GskTextNode *self = (const GskTextNode *) node;
|
|
|
|
return self->num_glyphs;
|
|
}
|
|
|
|
/**
|
|
* gsk_text_node_get_glyphs:
|
|
* @node: (type GskTextNode): a text `GskRenderNode`
|
|
* @n_glyphs: (out) (optional): the number of glyphs returned
|
|
*
|
|
* Retrieves the glyph information in the @node.
|
|
*
|
|
* Returns: (transfer none) (array length=n_glyphs): the glyph information
|
|
*/
|
|
const PangoGlyphInfo *
|
|
gsk_text_node_get_glyphs (const GskRenderNode *node,
|
|
guint *n_glyphs)
|
|
{
|
|
const GskTextNode *self = (const GskTextNode *) node;
|
|
|
|
if (n_glyphs != NULL)
|
|
*n_glyphs = self->num_glyphs;
|
|
|
|
return self->glyphs;
|
|
}
|
|
|
|
/**
|
|
* gsk_text_node_get_offset:
|
|
* @node: (type GskTextNode): a text `GskRenderNode`
|
|
*
|
|
* Retrieves the offset applied to the text.
|
|
*
|
|
* Returns: (transfer none): a point with the horizontal and vertical offsets
|
|
*/
|
|
const graphene_point_t *
|
|
gsk_text_node_get_offset (const GskRenderNode *node)
|
|
{
|
|
const GskTextNode *self = (const GskTextNode *) node;
|
|
|
|
return &self->offset;
|
|
}
|
|
|
|
/* }}} */
|
|
/* {{{ GSK_BLUR_NODE */
|
|
|
|
/**
|
|
* GskBlurNode:
|
|
*
|
|
* A render node applying a blur effect to its single child.
|
|
*/
|
|
struct _GskBlurNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
GskRenderNode *child;
|
|
float radius;
|
|
};
|
|
|
|
static void
|
|
gsk_blur_node_finalize (GskRenderNode *node)
|
|
{
|
|
GskBlurNode *self = (GskBlurNode *) node;
|
|
GskRenderNodeClass *parent_class = g_type_class_peek (g_type_parent (GSK_TYPE_BLUR_NODE));
|
|
|
|
gsk_render_node_unref (self->child);
|
|
|
|
parent_class->finalize (node);
|
|
}
|
|
|
|
static void
|
|
blur_once (cairo_surface_t *src,
|
|
cairo_surface_t *dest,
|
|
int radius,
|
|
guchar *div_kernel_size)
|
|
{
|
|
int width, height, src_rowstride, dest_rowstride, n_channels;
|
|
guchar *p_src, *p_dest, *c1, *c2;
|
|
int x, y, i, i1, i2, width_minus_1, height_minus_1, radius_plus_1;
|
|
int r, g, b, a;
|
|
guchar *p_dest_row, *p_dest_col;
|
|
|
|
width = cairo_image_surface_get_width (src);
|
|
height = cairo_image_surface_get_height (src);
|
|
n_channels = 4;
|
|
radius_plus_1 = radius + 1;
|
|
|
|
/* horizontal blur */
|
|
p_src = cairo_image_surface_get_data (src);
|
|
p_dest = cairo_image_surface_get_data (dest);
|
|
src_rowstride = cairo_image_surface_get_stride (src);
|
|
dest_rowstride = cairo_image_surface_get_stride (dest);
|
|
|
|
width_minus_1 = width - 1;
|
|
for (y = 0; y < height; y++)
|
|
{
|
|
/* calc the initial sums of the kernel */
|
|
r = g = b = a = 0;
|
|
for (i = -radius; i <= radius; i++)
|
|
{
|
|
c1 = p_src + (CLAMP (i, 0, width_minus_1) * n_channels);
|
|
r += c1[0];
|
|
g += c1[1];
|
|
b += c1[2];
|
|
a += c1[3];
|
|
}
|
|
p_dest_row = p_dest;
|
|
for (x = 0; x < width; x++)
|
|
{
|
|
/* set as the mean of the kernel */
|
|
p_dest_row[0] = div_kernel_size[r];
|
|
p_dest_row[1] = div_kernel_size[g];
|
|
p_dest_row[2] = div_kernel_size[b];
|
|
p_dest_row[3] = div_kernel_size[a];
|
|
p_dest_row += n_channels;
|
|
|
|
/* the pixel to add to the kernel */
|
|
i1 = x + radius_plus_1;
|
|
if (i1 > width_minus_1)
|
|
i1 = width_minus_1;
|
|
c1 = p_src + (i1 * n_channels);
|
|
|
|
/* the pixel to remove from the kernel */
|
|
i2 = x - radius;
|
|
if (i2 < 0)
|
|
i2 = 0;
|
|
c2 = p_src + (i2 * n_channels);
|
|
|
|
/* calc the new sums of the kernel */
|
|
r += c1[0] - c2[0];
|
|
g += c1[1] - c2[1];
|
|
b += c1[2] - c2[2];
|
|
a += c1[3] - c2[3];
|
|
}
|
|
|
|
p_src += src_rowstride;
|
|
p_dest += dest_rowstride;
|
|
}
|
|
|
|
/* vertical blur */
|
|
p_src = cairo_image_surface_get_data (dest);
|
|
p_dest = cairo_image_surface_get_data (src);
|
|
src_rowstride = cairo_image_surface_get_stride (dest);
|
|
dest_rowstride = cairo_image_surface_get_stride (src);
|
|
|
|
height_minus_1 = height - 1;
|
|
for (x = 0; x < width; x++)
|
|
{
|
|
/* calc the initial sums of the kernel */
|
|
r = g = b = a = 0;
|
|
for (i = -radius; i <= radius; i++)
|
|
{
|
|
c1 = p_src + (CLAMP (i, 0, height_minus_1) * src_rowstride);
|
|
r += c1[0];
|
|
g += c1[1];
|
|
b += c1[2];
|
|
a += c1[3];
|
|
}
|
|
|
|
p_dest_col = p_dest;
|
|
for (y = 0; y < height; y++)
|
|
{
|
|
/* set as the mean of the kernel */
|
|
|
|
p_dest_col[0] = div_kernel_size[r];
|
|
p_dest_col[1] = div_kernel_size[g];
|
|
p_dest_col[2] = div_kernel_size[b];
|
|
p_dest_col[3] = div_kernel_size[a];
|
|
p_dest_col += dest_rowstride;
|
|
|
|
/* the pixel to add to the kernel */
|
|
i1 = y + radius_plus_1;
|
|
if (i1 > height_minus_1)
|
|
i1 = height_minus_1;
|
|
c1 = p_src + (i1 * src_rowstride);
|
|
|
|
/* the pixel to remove from the kernel */
|
|
i2 = y - radius;
|
|
if (i2 < 0)
|
|
i2 = 0;
|
|
c2 = p_src + (i2 * src_rowstride);
|
|
/* calc the new sums of the kernel */
|
|
r += c1[0] - c2[0];
|
|
g += c1[1] - c2[1];
|
|
b += c1[2] - c2[2];
|
|
a += c1[3] - c2[3];
|
|
}
|
|
|
|
p_src += n_channels;
|
|
p_dest += n_channels;
|
|
}
|
|
}
|
|
|
|
static void
|
|
blur_image_surface (cairo_surface_t *surface, int radius, int iterations)
|
|
{
|
|
int kernel_size;
|
|
int i;
|
|
guchar *div_kernel_size;
|
|
cairo_surface_t *tmp;
|
|
int width, height;
|
|
|
|
g_assert (radius >= 0);
|
|
|
|
width = cairo_image_surface_get_width (surface);
|
|
height = cairo_image_surface_get_height (surface);
|
|
tmp = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
|
|
|
|
kernel_size = 2 * radius + 1;
|
|
div_kernel_size = g_new (guchar, 256 * kernel_size);
|
|
for (i = 0; i < 256 * kernel_size; i++)
|
|
div_kernel_size[i] = (guchar) (i / kernel_size);
|
|
|
|
while (iterations-- > 0)
|
|
blur_once (surface, tmp, radius, div_kernel_size);
|
|
|
|
g_free (div_kernel_size);
|
|
cairo_surface_destroy (tmp);
|
|
}
|
|
|
|
static void
|
|
gsk_blur_node_draw (GskRenderNode *node,
|
|
cairo_t *cr,
|
|
GdkColorState *ccs)
|
|
{
|
|
GskBlurNode *self = (GskBlurNode *) node;
|
|
cairo_surface_t *surface;
|
|
cairo_t *cr2;
|
|
graphene_rect_t blur_bounds;
|
|
double clip_radius;
|
|
|
|
clip_radius = gsk_cairo_blur_compute_pixels (0.5 * self->radius);
|
|
|
|
/* We need to extend the clip by the blur radius
|
|
* so we can blur pixels in that region */
|
|
_graphene_rect_init_from_clip_extents (&blur_bounds, cr);
|
|
graphene_rect_inset (&blur_bounds, - clip_radius, - clip_radius);
|
|
if (!gsk_rect_intersection (&blur_bounds, &node->bounds, &blur_bounds))
|
|
return;
|
|
|
|
surface = cairo_surface_create_similar_image (cairo_get_target (cr),
|
|
CAIRO_FORMAT_ARGB32,
|
|
ceil (blur_bounds.size.width),
|
|
ceil (blur_bounds.size.height));
|
|
cairo_surface_set_device_offset (surface,
|
|
- blur_bounds.origin.x,
|
|
- blur_bounds.origin.y);
|
|
|
|
cr2 = cairo_create (surface);
|
|
gsk_render_node_draw_ccs (self->child, cr2, ccs);
|
|
cairo_destroy (cr2);
|
|
|
|
blur_image_surface (surface, (int) ceil (0.5 * self->radius), 3);
|
|
cairo_surface_mark_dirty (surface);
|
|
|
|
cairo_set_source_surface (cr, surface, 0, 0);
|
|
cairo_rectangle (cr,
|
|
node->bounds.origin.x, node->bounds.origin.y,
|
|
node->bounds.size.width, node->bounds.size.height);
|
|
cairo_fill (cr);
|
|
|
|
cairo_surface_destroy (surface);
|
|
}
|
|
|
|
static void
|
|
gsk_blur_node_diff (GskRenderNode *node1,
|
|
GskRenderNode *node2,
|
|
GskDiffData *data)
|
|
{
|
|
GskBlurNode *self1 = (GskBlurNode *) node1;
|
|
GskBlurNode *self2 = (GskBlurNode *) node2;
|
|
|
|
if (self1->radius == self2->radius)
|
|
{
|
|
cairo_rectangle_int_t rect;
|
|
cairo_region_t *sub;
|
|
int i, n, clip_radius;
|
|
|
|
clip_radius = ceil (gsk_cairo_blur_compute_pixels (self1->radius / 2.0));
|
|
sub = cairo_region_create ();
|
|
gsk_render_node_diff (self1->child, self2->child, &(GskDiffData) { sub, data->surface });
|
|
|
|
n = cairo_region_num_rectangles (sub);
|
|
for (i = 0; i < n; i++)
|
|
{
|
|
cairo_region_get_rectangle (sub, i, &rect);
|
|
rect.x -= clip_radius;
|
|
rect.y -= clip_radius;
|
|
rect.width += 2 * clip_radius;
|
|
rect.height += 2 * clip_radius;
|
|
cairo_region_union_rectangle (data->region, &rect);
|
|
}
|
|
cairo_region_destroy (sub);
|
|
}
|
|
else
|
|
{
|
|
gsk_render_node_diff_impossible (node1, node2, data);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gsk_blur_node_class_init (gpointer g_class,
|
|
gpointer class_data)
|
|
{
|
|
GskRenderNodeClass *node_class = g_class;
|
|
|
|
node_class->node_type = GSK_BLUR_NODE;
|
|
|
|
node_class->finalize = gsk_blur_node_finalize;
|
|
node_class->draw = gsk_blur_node_draw;
|
|
node_class->diff = gsk_blur_node_diff;
|
|
}
|
|
|
|
/**
|
|
* gsk_blur_node_new:
|
|
* @child: the child node to blur
|
|
* @radius: the blur radius. Must be positive
|
|
*
|
|
* Creates a render node that blurs the child.
|
|
*
|
|
* Returns: (transfer full) (type GskBlurNode): a new `GskRenderNode`
|
|
*/
|
|
GskRenderNode *
|
|
gsk_blur_node_new (GskRenderNode *child,
|
|
float radius)
|
|
{
|
|
GskBlurNode *self;
|
|
GskRenderNode *node;
|
|
float clip_radius;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE (child), NULL);
|
|
g_return_val_if_fail (radius >= 0, NULL);
|
|
|
|
self = gsk_render_node_alloc (GSK_BLUR_NODE);
|
|
node = (GskRenderNode *) self;
|
|
node->offscreen_for_opacity = child->offscreen_for_opacity;
|
|
|
|
self->child = gsk_render_node_ref (child);
|
|
self->radius = radius;
|
|
|
|
clip_radius = gsk_cairo_blur_compute_pixels (radius / 2.0);
|
|
|
|
gsk_rect_init_from_rect (&node->bounds, &child->bounds);
|
|
graphene_rect_inset (&self->render_node.bounds, - clip_radius, - clip_radius);
|
|
|
|
node->preferred_depth = gsk_render_node_get_preferred_depth (child);
|
|
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* gsk_blur_node_get_child:
|
|
* @node: (type GskBlurNode): a blur `GskRenderNode`
|
|
*
|
|
* Retrieves the child `GskRenderNode` of the blur @node.
|
|
*
|
|
* Returns: (transfer none): the blurred child node
|
|
*/
|
|
GskRenderNode *
|
|
gsk_blur_node_get_child (const GskRenderNode *node)
|
|
{
|
|
const GskBlurNode *self = (const GskBlurNode *) node;
|
|
|
|
return self->child;
|
|
}
|
|
|
|
/**
|
|
* gsk_blur_node_get_radius:
|
|
* @node: (type GskBlurNode): a blur `GskRenderNode`
|
|
*
|
|
* Retrieves the blur radius of the @node.
|
|
*
|
|
* Returns: the blur radius
|
|
*/
|
|
float
|
|
gsk_blur_node_get_radius (const GskRenderNode *node)
|
|
{
|
|
const GskBlurNode *self = (const GskBlurNode *) node;
|
|
|
|
return self->radius;
|
|
}
|
|
|
|
/* }}} */
|
|
/* {{{ GSK_MASK_NODE */
|
|
|
|
/**
|
|
* GskMaskNode:
|
|
*
|
|
* A render node masking one child node with another.
|
|
*
|
|
* Since: 4.10
|
|
*/
|
|
typedef struct _GskMaskNode GskMaskNode;
|
|
|
|
struct _GskMaskNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
GskRenderNode *mask;
|
|
GskRenderNode *source;
|
|
GskMaskMode mask_mode;
|
|
};
|
|
|
|
static void
|
|
gsk_mask_node_finalize (GskRenderNode *node)
|
|
{
|
|
GskMaskNode *self = (GskMaskNode *) node;
|
|
GskRenderNodeClass *parent_class = g_type_class_peek (g_type_parent (GSK_TYPE_MASK_NODE));
|
|
|
|
gsk_render_node_unref (self->source);
|
|
gsk_render_node_unref (self->mask);
|
|
|
|
parent_class->finalize (node);
|
|
}
|
|
|
|
static void
|
|
apply_luminance_to_pattern (cairo_pattern_t *pattern,
|
|
gboolean invert_luminance)
|
|
{
|
|
cairo_surface_t *surface, *image_surface;
|
|
guchar *data;
|
|
gsize x, y, width, height, stride;
|
|
int red, green, blue, alpha, luminance;
|
|
guint32* pixel_data;
|
|
|
|
cairo_pattern_get_surface (pattern, &surface);
|
|
image_surface = cairo_surface_map_to_image (surface, NULL);
|
|
|
|
data = cairo_image_surface_get_data (image_surface);
|
|
width = cairo_image_surface_get_width (image_surface);
|
|
height = cairo_image_surface_get_height (image_surface);
|
|
stride = cairo_image_surface_get_stride (image_surface);
|
|
|
|
for (y = 0; y < height; y++)
|
|
{
|
|
pixel_data = (guint32 *) data;
|
|
for (x = 0; x < width; x++)
|
|
{
|
|
alpha = (pixel_data[x] >> 24) & 0xFF;
|
|
red = (pixel_data[x] >> 16) & 0xFF;
|
|
green = (pixel_data[x] >> 8) & 0xFF;
|
|
blue = (pixel_data[x] >> 0) & 0xFF;
|
|
|
|
luminance = 2126 * red + 7152 * green + 722 * blue;
|
|
if (invert_luminance)
|
|
luminance = 10000 * alpha - luminance;
|
|
luminance = (luminance + 5000) / 10000;
|
|
|
|
pixel_data[x] = luminance * 0x1010101;
|
|
}
|
|
data += stride;
|
|
}
|
|
|
|
cairo_surface_mark_dirty (image_surface);
|
|
cairo_surface_unmap_image (surface, image_surface);
|
|
/* https://gitlab.freedesktop.org/cairo/cairo/-/merge_requests/487 */
|
|
cairo_surface_mark_dirty (surface);
|
|
}
|
|
|
|
static void
|
|
gsk_mask_node_draw (GskRenderNode *node,
|
|
cairo_t *cr,
|
|
GdkColorState *ccs)
|
|
{
|
|
GskMaskNode *self = (GskMaskNode *) node;
|
|
cairo_pattern_t *mask_pattern;
|
|
graphene_matrix_t color_matrix;
|
|
graphene_vec4_t color_offset;
|
|
|
|
/* clip so the push_group() creates a smaller surface */
|
|
gdk_cairo_rect (cr, &node->bounds);
|
|
cairo_clip (cr);
|
|
|
|
if (has_empty_clip (cr))
|
|
return;
|
|
|
|
cairo_push_group (cr);
|
|
gsk_render_node_draw_ccs (self->source, cr, ccs);
|
|
cairo_pop_group_to_source (cr);
|
|
|
|
cairo_push_group (cr);
|
|
gsk_render_node_draw_ccs (self->mask, cr, ccs);
|
|
mask_pattern = cairo_pop_group (cr);
|
|
|
|
switch (self->mask_mode)
|
|
{
|
|
case GSK_MASK_MODE_ALPHA:
|
|
break;
|
|
case GSK_MASK_MODE_INVERTED_ALPHA:
|
|
graphene_matrix_init_from_float (&color_matrix, (float[]){ 0, 0, 0, 0,
|
|
0, 0, 0, 0,
|
|
0, 0, 0, 0,
|
|
-1, -1, -1, -1 });
|
|
graphene_vec4_init (&color_offset, 1, 1, 1, 1);
|
|
apply_color_matrix_to_pattern (mask_pattern, &color_matrix, &color_offset);
|
|
break;
|
|
case GSK_MASK_MODE_LUMINANCE:
|
|
apply_luminance_to_pattern (mask_pattern, FALSE);
|
|
break;
|
|
case GSK_MASK_MODE_INVERTED_LUMINANCE:
|
|
apply_luminance_to_pattern (mask_pattern, TRUE);
|
|
break;
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
gdk_cairo_rect (cr, &node->bounds);
|
|
cairo_clip (cr);
|
|
|
|
cairo_mask (cr, mask_pattern);
|
|
|
|
cairo_pattern_destroy (mask_pattern);
|
|
}
|
|
|
|
static void
|
|
gsk_mask_node_diff (GskRenderNode *node1,
|
|
GskRenderNode *node2,
|
|
GskDiffData *data)
|
|
{
|
|
GskMaskNode *self1 = (GskMaskNode *) node1;
|
|
GskMaskNode *self2 = (GskMaskNode *) node2;
|
|
|
|
if (self1->mask_mode != self2->mask_mode)
|
|
{
|
|
gsk_render_node_diff_impossible (node1, node2, data);
|
|
return;
|
|
}
|
|
|
|
gsk_render_node_diff (self1->source, self2->source, data);
|
|
gsk_render_node_diff (self1->mask, self2->mask, data);
|
|
}
|
|
|
|
static void
|
|
gsk_mask_node_class_init (gpointer g_class,
|
|
gpointer class_data)
|
|
{
|
|
GskRenderNodeClass *node_class = g_class;
|
|
|
|
node_class->node_type = GSK_MASK_NODE;
|
|
|
|
node_class->finalize = gsk_mask_node_finalize;
|
|
node_class->draw = gsk_mask_node_draw;
|
|
node_class->diff = gsk_mask_node_diff;
|
|
}
|
|
|
|
/**
|
|
* gsk_mask_node_new:
|
|
* @source: The source node to be drawn
|
|
* @mask: The node to be used as mask
|
|
* @mask_mode: The mask mode to use
|
|
*
|
|
* Creates a `GskRenderNode` that will mask a given node by another.
|
|
*
|
|
* The @mask_mode determines how the 'mask values' are derived from
|
|
* the colors of the @mask. Applying the mask consists of multiplying
|
|
* the 'mask value' with the alpha of the source.
|
|
*
|
|
* Returns: (transfer full) (type GskMaskNode): A new `GskRenderNode`
|
|
*
|
|
* Since: 4.10
|
|
*/
|
|
GskRenderNode *
|
|
gsk_mask_node_new (GskRenderNode *source,
|
|
GskRenderNode *mask,
|
|
GskMaskMode mask_mode)
|
|
{
|
|
GskMaskNode *self;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE (source), NULL);
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE (mask), NULL);
|
|
|
|
self = gsk_render_node_alloc (GSK_MASK_NODE);
|
|
self->source = gsk_render_node_ref (source);
|
|
self->mask = gsk_render_node_ref (mask);
|
|
self->mask_mode = mask_mode;
|
|
|
|
if (mask_mode == GSK_MASK_MODE_INVERTED_ALPHA)
|
|
self->render_node.bounds = source->bounds;
|
|
else if (!gsk_rect_intersection (&source->bounds, &mask->bounds, &self->render_node.bounds))
|
|
self->render_node.bounds = *graphene_rect_zero ();
|
|
|
|
self->render_node.preferred_depth = gsk_render_node_get_preferred_depth (source);
|
|
|
|
return &self->render_node;
|
|
}
|
|
|
|
/**
|
|
* gsk_mask_node_get_source:
|
|
* @node: (type GskMaskNode): a mask `GskRenderNode`
|
|
*
|
|
* Retrieves the source `GskRenderNode` child of the @node.
|
|
*
|
|
* Returns: (transfer none): the source child node
|
|
*
|
|
* Since: 4.10
|
|
*/
|
|
GskRenderNode *
|
|
gsk_mask_node_get_source (const GskRenderNode *node)
|
|
{
|
|
const GskMaskNode *self = (const GskMaskNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_MASK_NODE), NULL);
|
|
|
|
return self->source;
|
|
}
|
|
|
|
/**
|
|
* gsk_mask_node_get_mask:
|
|
* @node: (type GskMaskNode): a mask `GskRenderNode`
|
|
*
|
|
* Retrieves the mask `GskRenderNode` child of the @node.
|
|
*
|
|
* Returns: (transfer none): the mask child node
|
|
*
|
|
* Since: 4.10
|
|
*/
|
|
GskRenderNode *
|
|
gsk_mask_node_get_mask (const GskRenderNode *node)
|
|
{
|
|
const GskMaskNode *self = (const GskMaskNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_MASK_NODE), NULL);
|
|
|
|
return self->mask;
|
|
}
|
|
|
|
/**
|
|
* gsk_mask_node_get_mask_mode:
|
|
* @node: (type GskMaskNode): a blending `GskRenderNode`
|
|
*
|
|
* Retrieves the mask mode used by @node.
|
|
*
|
|
* Returns: the mask mode
|
|
*
|
|
* Since: 4.10
|
|
*/
|
|
GskMaskMode
|
|
gsk_mask_node_get_mask_mode (const GskRenderNode *node)
|
|
{
|
|
const GskMaskNode *self = (const GskMaskNode *) node;
|
|
|
|
return self->mask_mode;
|
|
}
|
|
|
|
/* }}} */
|
|
/* {{{ GSK_DEBUG_NODE */
|
|
|
|
/**
|
|
* GskDebugNode:
|
|
*
|
|
* A render node that emits a debugging message when drawing its
|
|
* child node.
|
|
*/
|
|
struct _GskDebugNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
GskRenderNode *child;
|
|
char *message;
|
|
};
|
|
|
|
static void
|
|
gsk_debug_node_finalize (GskRenderNode *node)
|
|
{
|
|
GskDebugNode *self = (GskDebugNode *) node;
|
|
GskRenderNodeClass *parent_class = g_type_class_peek (g_type_parent (GSK_TYPE_DEBUG_NODE));
|
|
|
|
gsk_render_node_unref (self->child);
|
|
g_free (self->message);
|
|
|
|
parent_class->finalize (node);
|
|
}
|
|
|
|
static void
|
|
gsk_debug_node_draw (GskRenderNode *node,
|
|
cairo_t *cr,
|
|
GdkColorState *ccs)
|
|
{
|
|
GskDebugNode *self = (GskDebugNode *) node;
|
|
|
|
gsk_render_node_draw_ccs (self->child, cr, ccs);
|
|
}
|
|
|
|
static gboolean
|
|
gsk_debug_node_can_diff (const GskRenderNode *node1,
|
|
const GskRenderNode *node2)
|
|
{
|
|
GskDebugNode *self1 = (GskDebugNode *) node1;
|
|
GskDebugNode *self2 = (GskDebugNode *) node2;
|
|
|
|
return gsk_render_node_can_diff (self1->child, self2->child);
|
|
}
|
|
|
|
static void
|
|
gsk_debug_node_diff (GskRenderNode *node1,
|
|
GskRenderNode *node2,
|
|
GskDiffData *data)
|
|
{
|
|
GskDebugNode *self1 = (GskDebugNode *) node1;
|
|
GskDebugNode *self2 = (GskDebugNode *) node2;
|
|
|
|
gsk_render_node_diff (self1->child, self2->child, data);
|
|
}
|
|
|
|
static gboolean
|
|
gsk_debug_node_get_opaque_rect (GskRenderNode *node,
|
|
graphene_rect_t *out_opaque)
|
|
{
|
|
GskDebugNode *self = (GskDebugNode *) node;
|
|
|
|
return gsk_render_node_get_opaque_rect (self->child, out_opaque);
|
|
}
|
|
|
|
static void
|
|
gsk_debug_node_class_init (gpointer g_class,
|
|
gpointer class_data)
|
|
{
|
|
GskRenderNodeClass *node_class = g_class;
|
|
|
|
node_class->node_type = GSK_DEBUG_NODE;
|
|
|
|
node_class->finalize = gsk_debug_node_finalize;
|
|
node_class->draw = gsk_debug_node_draw;
|
|
node_class->can_diff = gsk_debug_node_can_diff;
|
|
node_class->diff = gsk_debug_node_diff;
|
|
node_class->get_opaque_rect = gsk_debug_node_get_opaque_rect;
|
|
}
|
|
|
|
/**
|
|
* gsk_debug_node_new:
|
|
* @child: The child to add debug info for
|
|
* @message: (transfer full): The debug message
|
|
*
|
|
* Creates a `GskRenderNode` that will add debug information about
|
|
* the given @child.
|
|
*
|
|
* Adding this node has no visual effect.
|
|
*
|
|
* Returns: (transfer full) (type GskDebugNode): A new `GskRenderNode`
|
|
*/
|
|
GskRenderNode *
|
|
gsk_debug_node_new (GskRenderNode *child,
|
|
char *message)
|
|
{
|
|
GskDebugNode *self;
|
|
GskRenderNode *node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE (child), NULL);
|
|
|
|
self = gsk_render_node_alloc (GSK_DEBUG_NODE);
|
|
node = (GskRenderNode *) self;
|
|
node->offscreen_for_opacity = child->offscreen_for_opacity;
|
|
node->fully_opaque = child->fully_opaque;
|
|
|
|
self->child = gsk_render_node_ref (child);
|
|
self->message = message;
|
|
|
|
gsk_rect_init_from_rect (&node->bounds, &child->bounds);
|
|
|
|
node->preferred_depth = gsk_render_node_get_preferred_depth (child);
|
|
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* gsk_debug_node_get_child:
|
|
* @node: (type GskDebugNode): a debug `GskRenderNode`
|
|
*
|
|
* Gets the child node that is getting drawn by the given @node.
|
|
*
|
|
* Returns: (transfer none): the child `GskRenderNode`
|
|
**/
|
|
GskRenderNode *
|
|
gsk_debug_node_get_child (const GskRenderNode *node)
|
|
{
|
|
const GskDebugNode *self = (const GskDebugNode *) node;
|
|
|
|
return self->child;
|
|
}
|
|
|
|
/**
|
|
* gsk_debug_node_get_message:
|
|
* @node: (type GskDebugNode): a debug `GskRenderNode`
|
|
*
|
|
* Gets the debug message that was set on this node
|
|
*
|
|
* Returns: (transfer none): The debug message
|
|
**/
|
|
const char *
|
|
gsk_debug_node_get_message (const GskRenderNode *node)
|
|
{
|
|
const GskDebugNode *self = (const GskDebugNode *) node;
|
|
|
|
return self->message;
|
|
}
|
|
|
|
/* }}} */
|
|
/* {{{ GSK_GL_SHADER_NODE */
|
|
|
|
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
|
|
/**
|
|
* GskGLShaderNode:
|
|
*
|
|
* A render node using a GL shader when drawing its children nodes.
|
|
*/
|
|
struct _GskGLShaderNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
GskGLShader *shader;
|
|
GBytes *args;
|
|
GskRenderNode **children;
|
|
guint n_children;
|
|
};
|
|
|
|
static void
|
|
gsk_gl_shader_node_finalize (GskRenderNode *node)
|
|
{
|
|
GskGLShaderNode *self = (GskGLShaderNode *) node;
|
|
GskRenderNodeClass *parent_class = g_type_class_peek (g_type_parent (GSK_TYPE_GL_SHADER_NODE));
|
|
|
|
for (guint i = 0; i < self->n_children; i++)
|
|
gsk_render_node_unref (self->children[i]);
|
|
g_free (self->children);
|
|
|
|
g_bytes_unref (self->args);
|
|
|
|
g_object_unref (self->shader);
|
|
|
|
parent_class->finalize (node);
|
|
}
|
|
|
|
static void
|
|
gsk_gl_shader_node_draw (GskRenderNode *node,
|
|
cairo_t *cr,
|
|
GdkColorState *ccs)
|
|
{
|
|
GdkRGBA pink = { 255 / 255., 105 / 255., 180 / 255., 1.0 };
|
|
|
|
gdk_cairo_set_source_rgba_ccs (cr, ccs, &pink);
|
|
gdk_cairo_rect (cr, &node->bounds);
|
|
cairo_fill (cr);
|
|
}
|
|
|
|
static void
|
|
gsk_gl_shader_node_diff (GskRenderNode *node1,
|
|
GskRenderNode *node2,
|
|
GskDiffData *data)
|
|
{
|
|
GskGLShaderNode *self1 = (GskGLShaderNode *) node1;
|
|
GskGLShaderNode *self2 = (GskGLShaderNode *) node2;
|
|
|
|
if (gsk_rect_equal (&node1->bounds, &node2->bounds) &&
|
|
self1->shader == self2->shader &&
|
|
g_bytes_compare (self1->args, self2->args) == 0 &&
|
|
self1->n_children == self2->n_children)
|
|
{
|
|
cairo_region_t *child_region = cairo_region_create();
|
|
for (guint i = 0; i < self1->n_children; i++)
|
|
gsk_render_node_diff (self1->children[i], self2->children[i], &(GskDiffData) { child_region, data->surface });
|
|
if (!cairo_region_is_empty (child_region))
|
|
gsk_render_node_diff_impossible (node1, node2, data);
|
|
cairo_region_destroy (child_region);
|
|
}
|
|
else
|
|
{
|
|
gsk_render_node_diff_impossible (node1, node2, data);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gsk_gl_shader_node_class_init (gpointer g_class,
|
|
gpointer class_data)
|
|
{
|
|
GskRenderNodeClass *node_class = g_class;
|
|
|
|
node_class->node_type = GSK_GL_SHADER_NODE;
|
|
|
|
node_class->finalize = gsk_gl_shader_node_finalize;
|
|
node_class->draw = gsk_gl_shader_node_draw;
|
|
node_class->diff = gsk_gl_shader_node_diff;
|
|
}
|
|
|
|
/**
|
|
* gsk_gl_shader_node_new:
|
|
* @shader: the `GskGLShader`
|
|
* @bounds: the rectangle to render the shader into
|
|
* @args: Arguments for the uniforms
|
|
* @children: (nullable) (array length=n_children): array of child nodes,
|
|
* these will be rendered to textures and used as input.
|
|
* @n_children: Length of @children (currently the GL backend supports
|
|
* up to 4 children)
|
|
*
|
|
* Creates a `GskRenderNode` that will render the given @shader into the
|
|
* area given by @bounds.
|
|
*
|
|
* The @args is a block of data to use for uniform input, as per types and
|
|
* offsets defined by the @shader. Normally this is generated by
|
|
* [method@Gsk.GLShader.format_args] or [struct@Gsk.ShaderArgsBuilder].
|
|
*
|
|
* See [class@Gsk.GLShader] for details about how the shader should be written.
|
|
*
|
|
* All the children will be rendered into textures (if they aren't already
|
|
* `GskTextureNodes`, which will be used directly). These textures will be
|
|
* sent as input to the shader.
|
|
*
|
|
* If the renderer doesn't support GL shaders, or if there is any problem
|
|
* when compiling the shader, then the node will draw pink. You should use
|
|
* [method@Gsk.GLShader.compile] to ensure the @shader will work for the
|
|
* renderer before using it.
|
|
*
|
|
* Returns: (transfer full) (type GskGLShaderNode): A new `GskRenderNode`
|
|
*
|
|
* Deprecated: 4.16: GTK's new Vulkan-focused rendering
|
|
* does not support this feature. Use [GtkGLArea](../gtk4/class.GLArea.html)
|
|
* for OpenGL rendering.
|
|
*/
|
|
GskRenderNode *
|
|
gsk_gl_shader_node_new (GskGLShader *shader,
|
|
const graphene_rect_t *bounds,
|
|
GBytes *args,
|
|
GskRenderNode **children,
|
|
guint n_children)
|
|
{
|
|
GskGLShaderNode *self;
|
|
GskRenderNode *node;
|
|
|
|
g_return_val_if_fail (GSK_IS_GL_SHADER (shader), NULL);
|
|
g_return_val_if_fail (bounds != NULL, NULL);
|
|
g_return_val_if_fail (args != NULL, NULL);
|
|
g_return_val_if_fail (g_bytes_get_size (args) == gsk_gl_shader_get_args_size (shader), NULL);
|
|
g_return_val_if_fail ((children == NULL && n_children == 0) ||
|
|
(n_children == gsk_gl_shader_get_n_textures (shader)), NULL);
|
|
|
|
self = gsk_render_node_alloc (GSK_GL_SHADER_NODE);
|
|
node = (GskRenderNode *) self;
|
|
node->offscreen_for_opacity = TRUE;
|
|
node->preferred_depth = gdk_color_state_get_depth (GDK_COLOR_STATE_SRGB);
|
|
|
|
gsk_rect_init_from_rect (&node->bounds, bounds);
|
|
gsk_rect_normalize (&node->bounds);
|
|
|
|
self->shader = g_object_ref (shader);
|
|
|
|
self->args = g_bytes_ref (args);
|
|
|
|
self->n_children = n_children;
|
|
if (n_children > 0)
|
|
{
|
|
self->children = g_malloc_n (n_children, sizeof (GskRenderNode *));
|
|
for (guint i = 0; i < n_children; i++)
|
|
{
|
|
self->children[i] = gsk_render_node_ref (children[i]);
|
|
node->preferred_depth = gdk_memory_depth_merge (node->preferred_depth,
|
|
gsk_render_node_get_preferred_depth (children[i]));
|
|
}
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* gsk_gl_shader_node_get_n_children:
|
|
* @node: (type GskGLShaderNode): a `GskRenderNode` for a gl shader
|
|
*
|
|
* Returns the number of children
|
|
*
|
|
* Returns: The number of children
|
|
*
|
|
* Deprecated: 4.16: GTK's new Vulkan-focused rendering
|
|
* does not support this feature. Use [GtkGLArea](../gtk4/class.GLArea.html)
|
|
* for OpenGL rendering.
|
|
*/
|
|
guint
|
|
gsk_gl_shader_node_get_n_children (const GskRenderNode *node)
|
|
{
|
|
const GskGLShaderNode *self = (const GskGLShaderNode *) node;
|
|
|
|
return self->n_children;
|
|
}
|
|
|
|
/**
|
|
* gsk_gl_shader_node_get_child:
|
|
* @node: (type GskGLShaderNode): a `GskRenderNode` for a gl shader
|
|
* @idx: the position of the child to get
|
|
*
|
|
* Gets one of the children.
|
|
*
|
|
* Returns: (transfer none): the @idx'th child of @node
|
|
*
|
|
* Deprecated: 4.16: GTK's new Vulkan-focused rendering
|
|
* does not support this feature. Use [GtkGLArea](../gtk4/class.GLArea.html)
|
|
* for OpenGL rendering.
|
|
*/
|
|
GskRenderNode *
|
|
gsk_gl_shader_node_get_child (const GskRenderNode *node,
|
|
guint idx)
|
|
{
|
|
const GskGLShaderNode *self = (const GskGLShaderNode *) node;
|
|
|
|
return self->children[idx];
|
|
}
|
|
|
|
/**
|
|
* gsk_gl_shader_node_get_shader:
|
|
* @node: (type GskGLShaderNode): a `GskRenderNode` for a gl shader
|
|
*
|
|
* Gets shader code for the node.
|
|
*
|
|
* Returns: (transfer none): the `GskGLShader` shader
|
|
*/
|
|
GskGLShader *
|
|
gsk_gl_shader_node_get_shader (const GskRenderNode *node)
|
|
{
|
|
const GskGLShaderNode *self = (const GskGLShaderNode *) node;
|
|
|
|
return self->shader;
|
|
}
|
|
|
|
/**
|
|
* gsk_gl_shader_node_get_args:
|
|
* @node: (type GskGLShaderNode): a `GskRenderNode` for a gl shader
|
|
*
|
|
* Gets args for the node.
|
|
*
|
|
* Returns: (transfer none): A `GBytes` with the uniform arguments
|
|
*
|
|
* Deprecated: 4.16: GTK's new Vulkan-focused rendering
|
|
* does not support this feature. Use [GtkGLArea](../gtk4/class.GLArea.html)
|
|
* for OpenGL rendering.
|
|
*/
|
|
GBytes *
|
|
gsk_gl_shader_node_get_args (const GskRenderNode *node)
|
|
{
|
|
const GskGLShaderNode *self = (const GskGLShaderNode *) node;
|
|
|
|
return self->args;
|
|
}
|
|
G_GNUC_END_IGNORE_DEPRECATIONS
|
|
|
|
/* }}} */
|
|
/* {{{ GSK_SUBSURFACE_NODE */
|
|
|
|
/**
|
|
* GskSubsurfaceNode:
|
|
*
|
|
* A render node that potentially diverts a part of the scene graph to a subsurface.
|
|
*
|
|
* Since: 4.14
|
|
*/
|
|
struct _GskSubsurfaceNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
GskRenderNode *child;
|
|
GdkSubsurface *subsurface;
|
|
};
|
|
|
|
static void
|
|
gsk_subsurface_node_finalize (GskRenderNode *node)
|
|
{
|
|
GskSubsurfaceNode *self = (GskSubsurfaceNode *) node;
|
|
GskRenderNodeClass *parent_class = g_type_class_peek (g_type_parent (GSK_TYPE_SUBSURFACE_NODE));
|
|
|
|
gsk_render_node_unref (self->child);
|
|
g_clear_object (&self->subsurface);
|
|
|
|
parent_class->finalize (node);
|
|
}
|
|
|
|
static void
|
|
gsk_subsurface_node_draw (GskRenderNode *node,
|
|
cairo_t *cr,
|
|
GdkColorState *ccs)
|
|
{
|
|
GskSubsurfaceNode *self = (GskSubsurfaceNode *) node;
|
|
|
|
gsk_render_node_draw_ccs (self->child, cr, ccs);
|
|
}
|
|
|
|
static gboolean
|
|
gsk_subsurface_node_can_diff (const GskRenderNode *node1,
|
|
const GskRenderNode *node2)
|
|
{
|
|
GskSubsurfaceNode *self1 = (GskSubsurfaceNode *) node1;
|
|
GskSubsurfaceNode *self2 = (GskSubsurfaceNode *) node2;
|
|
|
|
return self1->subsurface == self2->subsurface;
|
|
}
|
|
|
|
static void
|
|
gsk_subsurface_node_diff (GskRenderNode *node1,
|
|
GskRenderNode *node2,
|
|
GskDiffData *data)
|
|
{
|
|
GskSubsurfaceNode *self1 = (GskSubsurfaceNode *) node1;
|
|
GskSubsurfaceNode *self2 = (GskSubsurfaceNode *) node2;
|
|
|
|
if (self1->subsurface != self2->subsurface)
|
|
{
|
|
/* Shouldn't happen, can_diff() avoids this, but to be sure */
|
|
gsk_render_node_diff_impossible (node1, node2, data);
|
|
}
|
|
else if (self1->subsurface && self1->subsurface->parent != data->surface)
|
|
{
|
|
/* The inspector case */
|
|
gsk_render_node_diff (self1->child, self2->child, data);
|
|
}
|
|
else if (self1->subsurface && gdk_subsurface_get_texture (self1->subsurface) != NULL)
|
|
{
|
|
/* offloaded, no contents to compare */
|
|
}
|
|
else
|
|
{
|
|
/* not offloaded, diff the children */
|
|
gsk_render_node_diff (self1->child, self2->child, data);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gsk_subsurface_node_get_opaque_rect (GskRenderNode *node,
|
|
graphene_rect_t *out_opaque)
|
|
{
|
|
GskSubsurfaceNode *self = (GskSubsurfaceNode *) node;
|
|
|
|
return gsk_render_node_get_opaque_rect (self->child, out_opaque);
|
|
}
|
|
|
|
static void
|
|
gsk_subsurface_node_class_init (gpointer g_class,
|
|
gpointer class_data)
|
|
{
|
|
GskRenderNodeClass *node_class = g_class;
|
|
|
|
node_class->node_type = GSK_SUBSURFACE_NODE;
|
|
|
|
node_class->finalize = gsk_subsurface_node_finalize;
|
|
node_class->draw = gsk_subsurface_node_draw;
|
|
node_class->can_diff = gsk_subsurface_node_can_diff;
|
|
node_class->diff = gsk_subsurface_node_diff;
|
|
node_class->get_opaque_rect = gsk_subsurface_node_get_opaque_rect;
|
|
}
|
|
|
|
/**
|
|
* gsk_subsurface_node_new: (skip)
|
|
* @child: The child to divert to a subsurface
|
|
* @subsurface: (nullable): the subsurface to use
|
|
*
|
|
* Creates a `GskRenderNode` that will possibly divert the child
|
|
* node to a subsurface.
|
|
*
|
|
* Note: Since subsurfaces are currently private, these nodes cannot
|
|
* currently be created outside of GTK. See
|
|
* [GtkGraphicsOffload](../gtk4/class.GraphicsOffload.html).
|
|
*
|
|
* Returns: (transfer full) (type GskSubsurfaceNode): A new `GskRenderNode`
|
|
*
|
|
* Since: 4.14
|
|
*/
|
|
GskRenderNode *
|
|
gsk_subsurface_node_new (GskRenderNode *child,
|
|
gpointer subsurface)
|
|
{
|
|
GskSubsurfaceNode *self;
|
|
GskRenderNode *node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE (child), NULL);
|
|
|
|
self = gsk_render_node_alloc (GSK_SUBSURFACE_NODE);
|
|
node = (GskRenderNode *) self;
|
|
node->offscreen_for_opacity = child->offscreen_for_opacity;
|
|
node->fully_opaque = child->fully_opaque;
|
|
|
|
self->child = gsk_render_node_ref (child);
|
|
if (subsurface)
|
|
self->subsurface = g_object_ref (subsurface);
|
|
else
|
|
self->subsurface = NULL;
|
|
|
|
gsk_rect_init_from_rect (&node->bounds, &child->bounds);
|
|
|
|
node->preferred_depth = gsk_render_node_get_preferred_depth (child);
|
|
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* gsk_subsurface_node_get_child:
|
|
* @node: (type GskSubsurfaceNode): a debug `GskRenderNode`
|
|
*
|
|
* Gets the child node that is getting drawn by the given @node.
|
|
*
|
|
* Returns: (transfer none): the child `GskRenderNode`
|
|
*
|
|
* Since: 4.14
|
|
*/
|
|
GskRenderNode *
|
|
gsk_subsurface_node_get_child (const GskRenderNode *node)
|
|
{
|
|
const GskSubsurfaceNode *self = (const GskSubsurfaceNode *) node;
|
|
|
|
return self->child;
|
|
}
|
|
|
|
/**
|
|
* gsk_subsurface_node_get_subsurface: (skip)
|
|
* @node: (type GskDebugNode): a debug `GskRenderNode`
|
|
*
|
|
* Gets the subsurface that was set on this node
|
|
*
|
|
* Returns: (transfer none) (nullable): the subsurface
|
|
*
|
|
* Since: 4.14
|
|
*/
|
|
gpointer
|
|
gsk_subsurface_node_get_subsurface (const GskRenderNode *node)
|
|
{
|
|
const GskSubsurfaceNode *self = (const GskSubsurfaceNode *) node;
|
|
|
|
return self->subsurface;
|
|
}
|
|
|
|
/* }}} */
|
|
|
|
GType gsk_render_node_types[GSK_RENDER_NODE_TYPE_N_TYPES];
|
|
|
|
#ifndef I_
|
|
# define I_(str) g_intern_static_string ((str))
|
|
#endif
|
|
|
|
#define GSK_DEFINE_RENDER_NODE_TYPE(type_name, TYPE_ENUM_VALUE) \
|
|
GType \
|
|
type_name ## _get_type (void) { \
|
|
gsk_render_node_init_types (); \
|
|
g_assert (gsk_render_node_types[TYPE_ENUM_VALUE] != G_TYPE_INVALID); \
|
|
return gsk_render_node_types[TYPE_ENUM_VALUE]; \
|
|
}
|
|
|
|
GSK_DEFINE_RENDER_NODE_TYPE (gsk_container_node, GSK_CONTAINER_NODE)
|
|
GSK_DEFINE_RENDER_NODE_TYPE (gsk_cairo_node, GSK_CAIRO_NODE)
|
|
GSK_DEFINE_RENDER_NODE_TYPE (gsk_color_node, GSK_COLOR_NODE)
|
|
GSK_DEFINE_RENDER_NODE_TYPE (gsk_linear_gradient_node, GSK_LINEAR_GRADIENT_NODE)
|
|
GSK_DEFINE_RENDER_NODE_TYPE (gsk_repeating_linear_gradient_node, GSK_REPEATING_LINEAR_GRADIENT_NODE)
|
|
GSK_DEFINE_RENDER_NODE_TYPE (gsk_radial_gradient_node, GSK_RADIAL_GRADIENT_NODE)
|
|
GSK_DEFINE_RENDER_NODE_TYPE (gsk_repeating_radial_gradient_node, GSK_REPEATING_RADIAL_GRADIENT_NODE)
|
|
GSK_DEFINE_RENDER_NODE_TYPE (gsk_conic_gradient_node, GSK_CONIC_GRADIENT_NODE)
|
|
GSK_DEFINE_RENDER_NODE_TYPE (gsk_border_node, GSK_BORDER_NODE)
|
|
GSK_DEFINE_RENDER_NODE_TYPE (gsk_texture_node, GSK_TEXTURE_NODE)
|
|
GSK_DEFINE_RENDER_NODE_TYPE (gsk_texture_scale_node, GSK_TEXTURE_SCALE_NODE)
|
|
GSK_DEFINE_RENDER_NODE_TYPE (gsk_inset_shadow_node, GSK_INSET_SHADOW_NODE)
|
|
GSK_DEFINE_RENDER_NODE_TYPE (gsk_outset_shadow_node, GSK_OUTSET_SHADOW_NODE)
|
|
GSK_DEFINE_RENDER_NODE_TYPE (gsk_transform_node, GSK_TRANSFORM_NODE)
|
|
GSK_DEFINE_RENDER_NODE_TYPE (gsk_opacity_node, GSK_OPACITY_NODE)
|
|
GSK_DEFINE_RENDER_NODE_TYPE (gsk_color_matrix_node, GSK_COLOR_MATRIX_NODE)
|
|
GSK_DEFINE_RENDER_NODE_TYPE (gsk_repeat_node, GSK_REPEAT_NODE)
|
|
GSK_DEFINE_RENDER_NODE_TYPE (gsk_clip_node, GSK_CLIP_NODE)
|
|
GSK_DEFINE_RENDER_NODE_TYPE (gsk_rounded_clip_node, GSK_ROUNDED_CLIP_NODE)
|
|
GSK_DEFINE_RENDER_NODE_TYPE (gsk_fill_node, GSK_FILL_NODE)
|
|
GSK_DEFINE_RENDER_NODE_TYPE (gsk_stroke_node, GSK_STROKE_NODE)
|
|
GSK_DEFINE_RENDER_NODE_TYPE (gsk_shadow_node, GSK_SHADOW_NODE)
|
|
GSK_DEFINE_RENDER_NODE_TYPE (gsk_blend_node, GSK_BLEND_NODE)
|
|
GSK_DEFINE_RENDER_NODE_TYPE (gsk_cross_fade_node, GSK_CROSS_FADE_NODE)
|
|
GSK_DEFINE_RENDER_NODE_TYPE (gsk_text_node, GSK_TEXT_NODE)
|
|
GSK_DEFINE_RENDER_NODE_TYPE (gsk_blur_node, GSK_BLUR_NODE)
|
|
GSK_DEFINE_RENDER_NODE_TYPE (gsk_mask_node, GSK_MASK_NODE)
|
|
GSK_DEFINE_RENDER_NODE_TYPE (gsk_gl_shader_node, GSK_GL_SHADER_NODE)
|
|
GSK_DEFINE_RENDER_NODE_TYPE (gsk_debug_node, GSK_DEBUG_NODE)
|
|
GSK_DEFINE_RENDER_NODE_TYPE (gsk_subsurface_node, GSK_SUBSURFACE_NODE)
|
|
|
|
static void
|
|
gsk_render_node_init_types_once (void)
|
|
{
|
|
GType node_type;
|
|
|
|
node_type = gsk_render_node_type_register_static (I_("GskContainerNode"),
|
|
sizeof (GskContainerNode),
|
|
gsk_container_node_class_init);
|
|
gsk_render_node_types[GSK_CONTAINER_NODE] = node_type;
|
|
|
|
node_type = gsk_render_node_type_register_static (I_("GskCairoNode"),
|
|
sizeof (GskCairoNode),
|
|
gsk_cairo_node_class_init);
|
|
gsk_render_node_types[GSK_CAIRO_NODE] = node_type;
|
|
|
|
node_type = gsk_render_node_type_register_static (I_("GskColorNode"),
|
|
sizeof (GskColorNode),
|
|
gsk_color_node_class_init);
|
|
gsk_render_node_types[GSK_COLOR_NODE] = node_type;
|
|
|
|
node_type = gsk_render_node_type_register_static (I_("GskLinearGradientNode"),
|
|
sizeof (GskLinearGradientNode),
|
|
gsk_linear_gradient_node_class_init);
|
|
gsk_render_node_types[GSK_LINEAR_GRADIENT_NODE] = node_type;
|
|
|
|
node_type = gsk_render_node_type_register_static (I_("GskRepeatingLinearGradientNode"),
|
|
sizeof (GskLinearGradientNode),
|
|
gsk_repeating_linear_gradient_node_class_init);
|
|
gsk_render_node_types[GSK_REPEATING_LINEAR_GRADIENT_NODE] = node_type;
|
|
|
|
node_type = gsk_render_node_type_register_static (I_("GskRadialGradientNode"),
|
|
sizeof (GskRadialGradientNode),
|
|
gsk_radial_gradient_node_class_init);
|
|
gsk_render_node_types[GSK_RADIAL_GRADIENT_NODE] = node_type;
|
|
|
|
node_type = gsk_render_node_type_register_static (I_("GskRepeatingRadialGradientNode"),
|
|
sizeof (GskRadialGradientNode),
|
|
gsk_repeating_radial_gradient_node_class_init);
|
|
gsk_render_node_types[GSK_REPEATING_RADIAL_GRADIENT_NODE] = node_type;
|
|
|
|
node_type = gsk_render_node_type_register_static (I_("GskConicGradientNode"),
|
|
sizeof (GskConicGradientNode),
|
|
gsk_conic_gradient_node_class_init);
|
|
gsk_render_node_types[GSK_CONIC_GRADIENT_NODE] = node_type;
|
|
|
|
node_type = gsk_render_node_type_register_static (I_("GskBorderNode"),
|
|
sizeof (GskBorderNode),
|
|
gsk_border_node_class_init);
|
|
gsk_render_node_types[GSK_BORDER_NODE] = node_type;
|
|
|
|
node_type = gsk_render_node_type_register_static (I_("GskTextureNode"),
|
|
sizeof (GskTextureNode),
|
|
gsk_texture_node_class_init);
|
|
gsk_render_node_types[GSK_TEXTURE_NODE] = node_type;
|
|
|
|
node_type = gsk_render_node_type_register_static (I_("GskTextureScaleNode"),
|
|
sizeof (GskTextureScaleNode),
|
|
gsk_texture_scale_node_class_init);
|
|
gsk_render_node_types[GSK_TEXTURE_SCALE_NODE] = node_type;
|
|
|
|
node_type = gsk_render_node_type_register_static (I_("GskInsetShadowNode"),
|
|
sizeof (GskInsetShadowNode),
|
|
gsk_inset_shadow_node_class_init);
|
|
gsk_render_node_types[GSK_INSET_SHADOW_NODE] = node_type;
|
|
|
|
node_type = gsk_render_node_type_register_static (I_("GskOutsetShadowNode"),
|
|
sizeof (GskOutsetShadowNode),
|
|
gsk_outset_shadow_node_class_init);
|
|
gsk_render_node_types[GSK_OUTSET_SHADOW_NODE] = node_type;
|
|
|
|
node_type = gsk_render_node_type_register_static (I_("GskTransformNode"),
|
|
sizeof (GskTransformNode),
|
|
gsk_transform_node_class_init);
|
|
gsk_render_node_types[GSK_TRANSFORM_NODE] = node_type;
|
|
|
|
node_type = gsk_render_node_type_register_static (I_("GskOpacityNode"),
|
|
sizeof (GskOpacityNode),
|
|
gsk_opacity_node_class_init);
|
|
gsk_render_node_types[GSK_OPACITY_NODE] = node_type;
|
|
|
|
node_type = gsk_render_node_type_register_static (I_("GskColorMatrixNode"),
|
|
sizeof (GskColorMatrixNode),
|
|
gsk_color_matrix_node_class_init);
|
|
gsk_render_node_types[GSK_COLOR_MATRIX_NODE] = node_type;
|
|
|
|
node_type = gsk_render_node_type_register_static (I_("GskRepeatNode"),
|
|
sizeof (GskRepeatNode),
|
|
gsk_repeat_node_class_init);
|
|
gsk_render_node_types[GSK_REPEAT_NODE] = node_type;
|
|
|
|
node_type = gsk_render_node_type_register_static (I_("GskClipNode"),
|
|
sizeof (GskClipNode),
|
|
gsk_clip_node_class_init);
|
|
gsk_render_node_types[GSK_CLIP_NODE] = node_type;
|
|
|
|
node_type = gsk_render_node_type_register_static (I_("GskRoundedClipNode"),
|
|
sizeof (GskRoundedClipNode),
|
|
gsk_rounded_clip_node_class_init);
|
|
gsk_render_node_types[GSK_ROUNDED_CLIP_NODE] = node_type;
|
|
|
|
node_type = gsk_render_node_type_register_static (I_("GskShadowNode"),
|
|
sizeof (GskShadowNode),
|
|
gsk_shadow_node_class_init);
|
|
gsk_render_node_types[GSK_SHADOW_NODE] = node_type;
|
|
|
|
node_type = gsk_render_node_type_register_static (I_("GskBlendNode"),
|
|
sizeof (GskBlendNode),
|
|
gsk_blend_node_class_init);
|
|
gsk_render_node_types[GSK_BLEND_NODE] = node_type;
|
|
|
|
node_type = gsk_render_node_type_register_static (I_("GskCrossFadeNode"),
|
|
sizeof (GskCrossFadeNode),
|
|
gsk_cross_fade_node_class_init);
|
|
gsk_render_node_types[GSK_CROSS_FADE_NODE] = node_type;
|
|
|
|
node_type = gsk_render_node_type_register_static (I_("GskTextNode"),
|
|
sizeof (GskTextNode),
|
|
gsk_text_node_class_init);
|
|
gsk_render_node_types[GSK_TEXT_NODE] = node_type;
|
|
|
|
node_type = gsk_render_node_type_register_static (I_("GskBlurNode"),
|
|
sizeof (GskBlurNode),
|
|
gsk_blur_node_class_init);
|
|
gsk_render_node_types[GSK_BLUR_NODE] = node_type;
|
|
|
|
node_type = gsk_render_node_type_register_static (I_("GskMaskNode"),
|
|
sizeof (GskMaskNode),
|
|
gsk_mask_node_class_init);
|
|
gsk_render_node_types[GSK_MASK_NODE] = node_type;
|
|
|
|
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
|
|
node_type = gsk_render_node_type_register_static (I_("GskGLShaderNode"),
|
|
sizeof (GskGLShaderNode),
|
|
gsk_gl_shader_node_class_init);
|
|
gsk_render_node_types[GSK_GL_SHADER_NODE] = node_type;
|
|
G_GNUC_END_IGNORE_DEPRECATIONS
|
|
|
|
node_type = gsk_render_node_type_register_static (I_("GskDebugNode"),
|
|
sizeof (GskDebugNode),
|
|
gsk_debug_node_class_init);
|
|
gsk_render_node_types[GSK_DEBUG_NODE] = node_type;
|
|
|
|
node_type = gsk_render_node_type_register_static (I_("GskFillNode"),
|
|
sizeof (GskFillNode),
|
|
gsk_fill_node_class_init);
|
|
gsk_render_node_types[GSK_FILL_NODE] = node_type;
|
|
|
|
node_type = gsk_render_node_type_register_static (I_("GskStrokeNode"),
|
|
sizeof (GskStrokeNode),
|
|
gsk_stroke_node_class_init);
|
|
gsk_render_node_types[GSK_STROKE_NODE] = node_type;
|
|
|
|
node_type = gsk_render_node_type_register_static (I_("GskSubsurfaceNode"),
|
|
sizeof (GskSubsurfaceNode),
|
|
gsk_subsurface_node_class_init);
|
|
gsk_render_node_types[GSK_SUBSURFACE_NODE] = node_type;
|
|
}
|
|
|
|
static void
|
|
gsk_render_node_serialize_bytes_finish (GObject *source,
|
|
GAsyncResult *result,
|
|
gpointer serializer)
|
|
{
|
|
GOutputStream *stream = G_OUTPUT_STREAM (source);
|
|
GError *error = NULL;
|
|
|
|
if (g_output_stream_splice_finish (stream, result, &error) < 0)
|
|
gdk_content_serializer_return_error (serializer, error);
|
|
else
|
|
gdk_content_serializer_return_success (serializer);
|
|
}
|
|
|
|
static void
|
|
gsk_render_node_serialize_bytes (GdkContentSerializer *serializer,
|
|
GBytes *bytes)
|
|
{
|
|
GInputStream *input;
|
|
|
|
input = g_memory_input_stream_new_from_bytes (bytes);
|
|
|
|
g_output_stream_splice_async (gdk_content_serializer_get_output_stream (serializer),
|
|
input,
|
|
G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE,
|
|
gdk_content_serializer_get_priority (serializer),
|
|
gdk_content_serializer_get_cancellable (serializer),
|
|
gsk_render_node_serialize_bytes_finish,
|
|
serializer);
|
|
g_object_unref (input);
|
|
g_bytes_unref (bytes);
|
|
}
|
|
|
|
#ifdef CAIRO_HAS_SVG_SURFACE
|
|
static cairo_status_t
|
|
gsk_render_node_cairo_serializer_write (gpointer user_data,
|
|
const unsigned char *data,
|
|
unsigned int length)
|
|
{
|
|
g_byte_array_append (user_data, data, length);
|
|
|
|
return CAIRO_STATUS_SUCCESS;
|
|
}
|
|
|
|
static void
|
|
gsk_render_node_svg_serializer (GdkContentSerializer *serializer)
|
|
{
|
|
GskRenderNode *node;
|
|
cairo_surface_t *surface;
|
|
cairo_t *cr;
|
|
graphene_rect_t bounds;
|
|
GByteArray *array;
|
|
|
|
node = gsk_value_get_render_node (gdk_content_serializer_get_value (serializer));
|
|
gsk_render_node_get_bounds (node, &bounds);
|
|
array = g_byte_array_new ();
|
|
|
|
surface = cairo_svg_surface_create_for_stream (gsk_render_node_cairo_serializer_write,
|
|
array,
|
|
bounds.size.width,
|
|
bounds.size.height);
|
|
cairo_svg_surface_set_document_unit (surface, CAIRO_SVG_UNIT_PX);
|
|
cairo_surface_set_device_offset (surface, -bounds.origin.x, -bounds.origin.y);
|
|
|
|
cr = cairo_create (surface);
|
|
gsk_render_node_draw (node, cr);
|
|
cairo_destroy (cr);
|
|
|
|
cairo_surface_finish (surface);
|
|
if (cairo_surface_status (surface) == CAIRO_STATUS_SUCCESS)
|
|
{
|
|
gsk_render_node_serialize_bytes (serializer, g_byte_array_free_to_bytes (array));
|
|
}
|
|
else
|
|
{
|
|
GError *error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
cairo_status_to_string (cairo_surface_status (surface)));
|
|
gdk_content_serializer_return_error (serializer, error);
|
|
g_byte_array_unref (array);
|
|
}
|
|
cairo_surface_destroy (surface);
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
gsk_render_node_png_serializer (GdkContentSerializer *serializer)
|
|
{
|
|
GskRenderNode *node;
|
|
GdkTexture *texture;
|
|
GskRenderer *renderer;
|
|
GBytes *bytes;
|
|
|
|
node = gsk_value_get_render_node (gdk_content_serializer_get_value (serializer));
|
|
|
|
renderer = gsk_gl_renderer_new ();
|
|
if (!gsk_renderer_realize (renderer, NULL, NULL))
|
|
{
|
|
g_object_unref (renderer);
|
|
renderer = gsk_cairo_renderer_new ();
|
|
if (!gsk_renderer_realize (renderer, NULL, NULL))
|
|
{
|
|
g_assert_not_reached ();
|
|
}
|
|
}
|
|
texture = gsk_renderer_render_texture (renderer, node, NULL);
|
|
gsk_renderer_unrealize (renderer);
|
|
g_object_unref (renderer);
|
|
|
|
bytes = gdk_texture_save_to_png_bytes (texture);
|
|
g_object_unref (texture);
|
|
|
|
gsk_render_node_serialize_bytes (serializer, bytes);
|
|
}
|
|
|
|
static void
|
|
gsk_render_node_content_serializer (GdkContentSerializer *serializer)
|
|
{
|
|
const GValue *value;
|
|
GskRenderNode *node;
|
|
GBytes *bytes;
|
|
|
|
value = gdk_content_serializer_get_value (serializer);
|
|
node = gsk_value_get_render_node (value);
|
|
bytes = gsk_render_node_serialize (node);
|
|
|
|
gsk_render_node_serialize_bytes (serializer, bytes);
|
|
}
|
|
|
|
static void
|
|
gsk_render_node_content_deserializer_finish (GObject *source,
|
|
GAsyncResult *result,
|
|
gpointer deserializer)
|
|
{
|
|
GOutputStream *stream = G_OUTPUT_STREAM (source);
|
|
GError *error = NULL;
|
|
gssize written;
|
|
GValue *value;
|
|
GskRenderNode *node;
|
|
GBytes *bytes;
|
|
|
|
written = g_output_stream_splice_finish (stream, result, &error);
|
|
if (written < 0)
|
|
{
|
|
gdk_content_deserializer_return_error (deserializer, error);
|
|
return;
|
|
}
|
|
|
|
bytes = g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (stream));
|
|
|
|
/* For now, we ignore any parsing errors. We might want to revisit that if it turns
|
|
* out copy/paste leads to too many errors */
|
|
node = gsk_render_node_deserialize (bytes, NULL, NULL);
|
|
|
|
value = gdk_content_deserializer_get_value (deserializer);
|
|
gsk_value_take_render_node (value, node);
|
|
|
|
gdk_content_deserializer_return_success (deserializer);
|
|
}
|
|
|
|
static void
|
|
gsk_render_node_content_deserializer (GdkContentDeserializer *deserializer)
|
|
{
|
|
GOutputStream *output;
|
|
|
|
output = g_memory_output_stream_new_resizable ();
|
|
|
|
g_output_stream_splice_async (output,
|
|
gdk_content_deserializer_get_input_stream (deserializer),
|
|
G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
|
|
gdk_content_deserializer_get_priority (deserializer),
|
|
gdk_content_deserializer_get_cancellable (deserializer),
|
|
gsk_render_node_content_deserializer_finish,
|
|
deserializer);
|
|
g_object_unref (output);
|
|
}
|
|
|
|
static void
|
|
gsk_render_node_init_content_serializers (void)
|
|
{
|
|
gdk_content_register_serializer (GSK_TYPE_RENDER_NODE,
|
|
"application/x-gtk-render-node",
|
|
gsk_render_node_content_serializer,
|
|
NULL,
|
|
NULL);
|
|
gdk_content_register_serializer (GSK_TYPE_RENDER_NODE,
|
|
"text/plain;charset=utf-8",
|
|
gsk_render_node_content_serializer,
|
|
NULL,
|
|
NULL);
|
|
/* The serialization format only outputs ASCII, so we can do this */
|
|
gdk_content_register_serializer (GSK_TYPE_RENDER_NODE,
|
|
"text/plain",
|
|
gsk_render_node_content_serializer,
|
|
NULL,
|
|
NULL);
|
|
#ifdef CAIRO_HAS_SVG_SURFACE
|
|
gdk_content_register_serializer (GSK_TYPE_RENDER_NODE,
|
|
"image/svg+xml",
|
|
gsk_render_node_svg_serializer,
|
|
NULL,
|
|
NULL);
|
|
#endif
|
|
gdk_content_register_serializer (GSK_TYPE_RENDER_NODE,
|
|
"image/png",
|
|
gsk_render_node_png_serializer,
|
|
NULL,
|
|
NULL);
|
|
|
|
gdk_content_register_deserializer ("application/x-gtk-render-node",
|
|
GSK_TYPE_RENDER_NODE,
|
|
gsk_render_node_content_deserializer,
|
|
NULL,
|
|
NULL);
|
|
}
|
|
|
|
/*< private >
|
|
* gsk_render_node_init_types:
|
|
*
|
|
* Initialize all the `GskRenderNode` types provided by GSK.
|
|
*/
|
|
void
|
|
gsk_render_node_init_types (void)
|
|
{
|
|
static gsize register_types__volatile;
|
|
|
|
if (g_once_init_enter (®ister_types__volatile))
|
|
{
|
|
gboolean initialized = TRUE;
|
|
gsk_render_node_init_types_once ();
|
|
gsk_render_node_init_content_serializers ();
|
|
g_once_init_leave (®ister_types__volatile, initialized);
|
|
}
|
|
}
|
|
|
|
/* vim:set foldmethod=marker expandtab: */
|