forked from AuroraMiddleware/gtk
247fc2e471
We have code in place to handle a NULL node in the state when dealing with blend nodes, but we don't always check for NULL, which leads to warnings in the CSS Blend modes demo.
2476 lines
78 KiB
C
2476 lines
78 KiB
C
/* GTK - The GIMP Toolkit
|
|
* Copyright (C) 2016 Benjamin Otte <otte@gnome.org>
|
|
*
|
|
* 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 "gtksnapshot.h"
|
|
#include "gtksnapshotprivate.h"
|
|
|
|
#include "gtkcsscolorvalueprivate.h"
|
|
#include "gtkcssshadowvalueprivate.h"
|
|
#include "gtkdebug.h"
|
|
#include "gtkrenderbackgroundprivate.h"
|
|
#include "gtkrenderborderprivate.h"
|
|
#include "gtkrendericonprivate.h"
|
|
#include "gtkrendernodepaintableprivate.h"
|
|
#include "gtkstylecontextprivate.h"
|
|
#include "gsktransformprivate.h"
|
|
|
|
#include "gsk/gskrendernodeprivate.h"
|
|
|
|
#include "gtk/gskpango.h"
|
|
|
|
#define GDK_ARRAY_NAME gtk_snapshot_nodes
|
|
#define GDK_ARRAY_TYPE_NAME GtkSnapshotNodes
|
|
#define GDK_ARRAY_ELEMENT_TYPE GskRenderNode *
|
|
#define GDK_ARRAY_FREE_FUNC gsk_render_node_unref
|
|
#include "gdk/gdkarrayimpl.c"
|
|
|
|
/**
|
|
* SECTION:gtksnapshot
|
|
* @Short_description: Auxiliary object for snapshots
|
|
* @Title: GtkSnapshot
|
|
*
|
|
* GtkSnapshot is an auxiliary object that assists in creating #GskRenderNodes
|
|
* in the #GdkPaintableInterface.snapshot() vfunc. It functions in a similar way to
|
|
* a cairo context, and maintains a stack of render nodes and their associated
|
|
* transformations.
|
|
*
|
|
* The node at the top of the stack is the the one that gtk_snapshot_append_…
|
|
* functions operate on. Use the gtk_snapshot_push_… functions and gtk_snapshot_pop()
|
|
* to change the current node.
|
|
*
|
|
* The typical way to obtain a GtkSnapshot object is as an argument to
|
|
* the #GtkWidgetClass.snapshot() vfunc. If you need to create your own GtkSnapshot,
|
|
* use gtk_snapshot_new().
|
|
*/
|
|
|
|
typedef struct _GtkSnapshotState GtkSnapshotState;
|
|
|
|
typedef GskRenderNode * (* GtkSnapshotCollectFunc) (GtkSnapshot *snapshot,
|
|
GtkSnapshotState *state,
|
|
GskRenderNode **nodes,
|
|
guint n_nodes);
|
|
typedef void (* GtkSnapshotClearFunc) (GtkSnapshotState *state);
|
|
|
|
struct _GtkSnapshotState {
|
|
guint start_node_index;
|
|
guint n_nodes;
|
|
|
|
GskTransform * transform;
|
|
|
|
GtkSnapshotCollectFunc collect_func;
|
|
GtkSnapshotClearFunc clear_func;
|
|
union {
|
|
struct {
|
|
double opacity;
|
|
} opacity;
|
|
struct {
|
|
double radius;
|
|
} blur;
|
|
struct {
|
|
graphene_matrix_t matrix;
|
|
graphene_vec4_t offset;
|
|
} color_matrix;
|
|
struct {
|
|
graphene_rect_t bounds;
|
|
graphene_rect_t child_bounds;
|
|
} repeat;
|
|
struct {
|
|
graphene_rect_t bounds;
|
|
} clip;
|
|
struct {
|
|
GskGLShader *shader;
|
|
GBytes *args;
|
|
graphene_rect_t bounds;
|
|
GskRenderNode **nodes;
|
|
GskRenderNode *internal_nodes[4];
|
|
} glshader;
|
|
struct {
|
|
graphene_rect_t bounds;
|
|
int node_idx;
|
|
int n_children;
|
|
} glshader_texture;
|
|
struct {
|
|
GskRoundedRect bounds;
|
|
} rounded_clip;
|
|
struct {
|
|
gsize n_shadows;
|
|
GskShadow *shadows;
|
|
GskShadow a_shadow; /* Used if n_shadows == 1 */
|
|
} shadow;
|
|
struct {
|
|
GskBlendMode blend_mode;
|
|
GskRenderNode *bottom_node;
|
|
} blend;
|
|
struct {
|
|
double progress;
|
|
GskRenderNode *start_node;
|
|
} cross_fade;
|
|
struct {
|
|
char *message;
|
|
} debug;
|
|
} data;
|
|
};
|
|
|
|
static void gtk_snapshot_state_clear (GtkSnapshotState *state);
|
|
|
|
#define GDK_ARRAY_NAME gtk_snapshot_states
|
|
#define GDK_ARRAY_TYPE_NAME GtkSnapshotStates
|
|
#define GDK_ARRAY_ELEMENT_TYPE GtkSnapshotState
|
|
#define GDK_ARRAY_FREE_FUNC gtk_snapshot_state_clear
|
|
#define GDK_ARRAY_BY_VALUE 1
|
|
#define GDK_ARRAY_PREALLOC 16
|
|
#define GDK_ARRAY_NO_MEMSET 1
|
|
#include "gdk/gdkarrayimpl.c"
|
|
|
|
/* This is a nasty little hack. We typedef GtkSnapshot to the fake object GdkSnapshot
|
|
* so that we don't need to typecast between them.
|
|
* After all, the GdkSnapshot only exist so poor language bindings don't trip. Hardcore
|
|
* C code can just blatantly ignore such layering violations with a typedef.
|
|
*/
|
|
struct _GdkSnapshot {
|
|
GObject parent_instance; /* it's really GdkSnapshot, but don't tell anyone! */
|
|
|
|
GtkSnapshotStates state_stack;
|
|
GtkSnapshotNodes nodes;
|
|
};
|
|
|
|
struct _GtkSnapshotClass {
|
|
GObjectClass parent_class; /* it's really GdkSnapshotClass, but don't tell anyone! */
|
|
};
|
|
|
|
G_DEFINE_TYPE (GtkSnapshot, gtk_snapshot, GDK_TYPE_SNAPSHOT)
|
|
|
|
static void
|
|
gtk_snapshot_dispose (GObject *object)
|
|
{
|
|
GtkSnapshot *snapshot = GTK_SNAPSHOT (object);
|
|
|
|
if (!gtk_snapshot_states_is_empty (&snapshot->state_stack))
|
|
gsk_render_node_unref (gtk_snapshot_to_node (snapshot));
|
|
|
|
g_assert (gtk_snapshot_states_is_empty (&snapshot->state_stack));
|
|
g_assert (gtk_snapshot_nodes_is_empty (&snapshot->nodes));
|
|
|
|
G_OBJECT_CLASS (gtk_snapshot_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
gtk_snapshot_class_init (GtkSnapshotClass *klass)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
|
|
gobject_class->dispose = gtk_snapshot_dispose;
|
|
}
|
|
|
|
static void
|
|
gtk_snapshot_init (GtkSnapshot *self)
|
|
{
|
|
}
|
|
|
|
static GskRenderNode *
|
|
gtk_snapshot_collect_default (GtkSnapshot *snapshot,
|
|
GtkSnapshotState *state,
|
|
GskRenderNode **nodes,
|
|
guint n_nodes)
|
|
{
|
|
GskRenderNode *node;
|
|
|
|
if (n_nodes == 0)
|
|
{
|
|
node = NULL;
|
|
}
|
|
else if (n_nodes == 1)
|
|
{
|
|
node = gsk_render_node_ref (nodes[0]);
|
|
}
|
|
else
|
|
{
|
|
node = gsk_container_node_new (nodes, n_nodes);
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
static GtkSnapshotState *
|
|
gtk_snapshot_push_state (GtkSnapshot *snapshot,
|
|
GskTransform *transform,
|
|
GtkSnapshotCollectFunc collect_func,
|
|
GtkSnapshotClearFunc clear_func)
|
|
{
|
|
const gsize n_states = gtk_snapshot_states_get_size (&snapshot->state_stack);
|
|
GtkSnapshotState *state;
|
|
|
|
gtk_snapshot_states_set_size (&snapshot->state_stack, n_states + 1);
|
|
state = gtk_snapshot_states_get (&snapshot->state_stack, n_states);
|
|
|
|
state->transform = gsk_transform_ref (transform);
|
|
state->collect_func = collect_func;
|
|
state->clear_func = clear_func;
|
|
state->start_node_index = gtk_snapshot_nodes_get_size (&snapshot->nodes);
|
|
state->n_nodes = 0;
|
|
|
|
return state;
|
|
}
|
|
|
|
static GtkSnapshotState *
|
|
gtk_snapshot_get_current_state (const GtkSnapshot *snapshot)
|
|
{
|
|
gsize size = gtk_snapshot_states_get_size (&snapshot->state_stack);
|
|
|
|
g_assert (size > 0);
|
|
|
|
return gtk_snapshot_states_get (&snapshot->state_stack, size - 1);
|
|
}
|
|
|
|
static GtkSnapshotState *
|
|
gtk_snapshot_get_previous_state (const GtkSnapshot *snapshot)
|
|
{
|
|
gsize size = gtk_snapshot_states_get_size (&snapshot->state_stack);
|
|
|
|
g_assert (size > 1);
|
|
|
|
return gtk_snapshot_states_get (&snapshot->state_stack, size - 2);
|
|
}
|
|
|
|
/* n == 0 => current, n == 1, previous, etc */
|
|
static GtkSnapshotState *
|
|
gtk_snapshot_get_nth_previous_state (const GtkSnapshot *snapshot,
|
|
int n)
|
|
{
|
|
gsize size = gtk_snapshot_states_get_size (&snapshot->state_stack);
|
|
|
|
g_assert (size > n);
|
|
|
|
return gtk_snapshot_states_get (&snapshot->state_stack, size - (1 + n));
|
|
}
|
|
|
|
static void
|
|
gtk_snapshot_state_clear (GtkSnapshotState *state)
|
|
{
|
|
if (state->clear_func)
|
|
state->clear_func (state);
|
|
|
|
gsk_transform_unref (state->transform);
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_new:
|
|
*
|
|
* Creates a new #GtkSnapshot.
|
|
*
|
|
* Returns: a newly-allocated #GtkSnapshot
|
|
*/
|
|
GtkSnapshot *
|
|
gtk_snapshot_new (void)
|
|
{
|
|
GtkSnapshot *snapshot;
|
|
|
|
snapshot = g_object_new (GTK_TYPE_SNAPSHOT, NULL);
|
|
|
|
gtk_snapshot_states_init (&snapshot->state_stack);
|
|
gtk_snapshot_nodes_init (&snapshot->nodes);
|
|
|
|
gtk_snapshot_push_state (snapshot,
|
|
NULL,
|
|
gtk_snapshot_collect_default,
|
|
NULL);
|
|
|
|
return snapshot;
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_free_to_node: (skip)
|
|
* @snapshot: (transfer full): a #GtkSnapshot
|
|
*
|
|
* Returns the node that was constructed by @snapshot
|
|
* and frees @snapshot.
|
|
*
|
|
* Returns: (transfer full): a newly-created #GskRenderNode
|
|
*/
|
|
GskRenderNode *
|
|
gtk_snapshot_free_to_node (GtkSnapshot *snapshot)
|
|
{
|
|
GskRenderNode *result;
|
|
|
|
result = gtk_snapshot_to_node (snapshot);
|
|
g_object_unref (snapshot);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_free_to_paintable: (skip)
|
|
* @snapshot: (transfer full): a #GtkSnapshot
|
|
* @size: (allow-none): The size of the resulting paintable
|
|
* or %NULL to use the bounds of the snapshot
|
|
*
|
|
* Returns a paintable for the node that was
|
|
* constructed by @snapshot and frees @snapshot.
|
|
*
|
|
* Returns: (transfer full): a newly-created #GdkPaintable
|
|
*/
|
|
GdkPaintable *
|
|
gtk_snapshot_free_to_paintable (GtkSnapshot *snapshot,
|
|
const graphene_size_t *size)
|
|
{
|
|
GdkPaintable *result;
|
|
|
|
result = gtk_snapshot_to_paintable (snapshot, size);
|
|
g_object_unref (snapshot);
|
|
|
|
return result;
|
|
}
|
|
|
|
static GskRenderNode *
|
|
gtk_snapshot_collect_autopush_transform (GtkSnapshot *snapshot,
|
|
GtkSnapshotState *state,
|
|
GskRenderNode **nodes,
|
|
guint n_nodes)
|
|
{
|
|
GskRenderNode *node, *transform_node;
|
|
GtkSnapshotState *previous_state;
|
|
|
|
previous_state = gtk_snapshot_get_previous_state (snapshot);
|
|
|
|
node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
|
|
if (node == NULL)
|
|
return NULL;
|
|
|
|
transform_node = gsk_transform_node_new (node, previous_state->transform);
|
|
|
|
gsk_render_node_unref (node);
|
|
|
|
return transform_node;
|
|
}
|
|
|
|
static void
|
|
gtk_snapshot_autopush_transform (GtkSnapshot *snapshot)
|
|
{
|
|
gtk_snapshot_push_state (snapshot,
|
|
NULL,
|
|
gtk_snapshot_collect_autopush_transform,
|
|
NULL);
|
|
}
|
|
|
|
static gboolean
|
|
gtk_snapshot_state_should_autopop (const GtkSnapshotState *state)
|
|
{
|
|
return state->collect_func == gtk_snapshot_collect_autopush_transform;
|
|
}
|
|
|
|
static GskRenderNode *
|
|
gtk_snapshot_collect_debug (GtkSnapshot *snapshot,
|
|
GtkSnapshotState *state,
|
|
GskRenderNode **nodes,
|
|
guint n_nodes)
|
|
{
|
|
GskRenderNode *node, *debug_node;
|
|
|
|
node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
|
|
if (node == NULL)
|
|
return NULL;
|
|
|
|
debug_node = gsk_debug_node_new (node, state->data.debug.message);
|
|
state->data.debug.message = NULL;
|
|
|
|
gsk_render_node_unref (node);
|
|
|
|
return debug_node;
|
|
}
|
|
|
|
static void
|
|
gtk_snapshot_clear_debug (GtkSnapshotState *state)
|
|
{
|
|
g_clear_pointer (&state->data.debug.message, g_free);
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_push_debug:
|
|
* @snapshot: a #GtkSnapshot
|
|
* @message: a printf-style format string
|
|
* @...: arguments for @message
|
|
*
|
|
* Inserts a debug node with a message. Debug nodes don't affect
|
|
* the rendering at all, but can be helpful in identifying parts
|
|
* of a render node tree dump, for example in the GTK inspector.
|
|
*/
|
|
void
|
|
gtk_snapshot_push_debug (GtkSnapshot *snapshot,
|
|
const char *message,
|
|
...)
|
|
{
|
|
GtkSnapshotState *current_state = gtk_snapshot_get_current_state (snapshot);
|
|
|
|
if (GTK_DEBUG_CHECK (SNAPSHOT))
|
|
{
|
|
va_list args;
|
|
GtkSnapshotState *state;
|
|
|
|
state = gtk_snapshot_push_state (snapshot,
|
|
current_state->transform,
|
|
gtk_snapshot_collect_debug,
|
|
gtk_snapshot_clear_debug);
|
|
|
|
|
|
|
|
va_start (args, message);
|
|
state->data.debug.message = g_strdup_vprintf (message, args);
|
|
va_end (args);
|
|
}
|
|
else
|
|
{
|
|
gtk_snapshot_push_state (snapshot,
|
|
current_state->transform,
|
|
gtk_snapshot_collect_default,
|
|
NULL);
|
|
}
|
|
}
|
|
|
|
static GskRenderNode *
|
|
gtk_snapshot_collect_opacity (GtkSnapshot *snapshot,
|
|
GtkSnapshotState *state,
|
|
GskRenderNode **nodes,
|
|
guint n_nodes)
|
|
{
|
|
GskRenderNode *node, *opacity_node;
|
|
|
|
node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
|
|
if (node == NULL)
|
|
return NULL;
|
|
|
|
if (state->data.opacity.opacity == 1.0)
|
|
{
|
|
opacity_node = node;
|
|
}
|
|
else if (state->data.opacity.opacity == 0.0)
|
|
{
|
|
gsk_render_node_unref (node);
|
|
opacity_node = NULL;
|
|
}
|
|
else
|
|
{
|
|
opacity_node = gsk_opacity_node_new (node, state->data.opacity.opacity);
|
|
gsk_render_node_unref (node);
|
|
}
|
|
|
|
return opacity_node;
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_push_opacity:
|
|
* @snapshot: a #GtkSnapshot
|
|
* @opacity: the opacity to use
|
|
*
|
|
* Modifies the opacity of an image.
|
|
*
|
|
* The image is recorded until the next call to gtk_snapshot_pop().
|
|
*/
|
|
void
|
|
gtk_snapshot_push_opacity (GtkSnapshot *snapshot,
|
|
double opacity)
|
|
{
|
|
GtkSnapshotState *current_state = gtk_snapshot_get_current_state (snapshot);
|
|
GtkSnapshotState *state;
|
|
|
|
state = gtk_snapshot_push_state (snapshot,
|
|
current_state->transform,
|
|
gtk_snapshot_collect_opacity,
|
|
NULL);
|
|
state->data.opacity.opacity = CLAMP (opacity, 0.0, 1.0);
|
|
}
|
|
|
|
static GskRenderNode *
|
|
gtk_snapshot_collect_blur (GtkSnapshot *snapshot,
|
|
GtkSnapshotState *state,
|
|
GskRenderNode **nodes,
|
|
guint n_nodes)
|
|
{
|
|
GskRenderNode *node, *blur_node;
|
|
double radius;
|
|
|
|
node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
|
|
if (node == NULL)
|
|
return NULL;
|
|
|
|
radius = state->data.blur.radius;
|
|
|
|
if (radius == 0.0)
|
|
return node;
|
|
|
|
blur_node = gsk_blur_node_new (node, radius);
|
|
|
|
gsk_render_node_unref (node);
|
|
|
|
return blur_node;
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_push_blur:
|
|
* @snapshot: a #GtkSnapshot
|
|
* @radius: the blur radius to use
|
|
*
|
|
* Blurs an image.
|
|
*
|
|
* The image is recorded until the next call to gtk_snapshot_pop().
|
|
*/
|
|
void
|
|
gtk_snapshot_push_blur (GtkSnapshot *snapshot,
|
|
double radius)
|
|
{
|
|
const GtkSnapshotState *current_state = gtk_snapshot_get_current_state (snapshot);
|
|
GtkSnapshotState *state;
|
|
|
|
state = gtk_snapshot_push_state (snapshot,
|
|
current_state->transform,
|
|
gtk_snapshot_collect_blur,
|
|
NULL);
|
|
state->data.blur.radius = radius;
|
|
}
|
|
|
|
static GskRenderNode *
|
|
merge_color_matrix_nodes (const graphene_matrix_t *matrix2,
|
|
const graphene_vec4_t *offset2,
|
|
GskRenderNode *child)
|
|
{
|
|
const graphene_matrix_t *mat1 = gsk_color_matrix_node_get_color_matrix (child);
|
|
const graphene_vec4_t *offset1 = gsk_color_matrix_node_get_color_offset (child);
|
|
graphene_matrix_t mat2 = *matrix2;
|
|
graphene_vec4_t off2 = *offset2;
|
|
GskRenderNode *result;
|
|
|
|
g_assert (gsk_render_node_get_node_type (child) == GSK_COLOR_MATRIX_NODE);
|
|
|
|
/* color matrix node: color = mat * p + offset; for a pixel p.
|
|
* color = mat1 * (mat2 * p + offset2) + offset1;
|
|
* = mat1 * mat2 * p + offset2 * mat1 + offset1
|
|
* = (mat1 * mat2) * p + (offset2 * mat1 + offset1)
|
|
* Which this code does.
|
|
* mat1 and offset1 come from @child.
|
|
*/
|
|
|
|
graphene_matrix_transform_vec4 (mat1, offset2, &off2);
|
|
graphene_vec4_add (&off2, offset1, &off2);
|
|
|
|
graphene_matrix_multiply (mat1, &mat2, &mat2);
|
|
|
|
result = gsk_color_matrix_node_new (gsk_color_matrix_node_get_child (child),
|
|
&mat2, &off2);
|
|
|
|
return result;
|
|
}
|
|
|
|
static GskRenderNode *
|
|
gtk_snapshot_collect_color_matrix (GtkSnapshot *snapshot,
|
|
GtkSnapshotState *state,
|
|
GskRenderNode **nodes,
|
|
guint n_nodes)
|
|
{
|
|
GskRenderNode *node, *result;
|
|
|
|
node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
|
|
if (node == NULL)
|
|
return NULL;
|
|
|
|
if (gsk_render_node_get_node_type (node) == GSK_COLOR_MATRIX_NODE)
|
|
{
|
|
result = merge_color_matrix_nodes (&state->data.color_matrix.matrix,
|
|
&state->data.color_matrix.offset,
|
|
node);
|
|
gsk_render_node_unref (node);
|
|
}
|
|
else if (gsk_render_node_get_node_type (node) == GSK_TRANSFORM_NODE)
|
|
{
|
|
GskRenderNode *transform_child = gsk_transform_node_get_child (node);
|
|
GskRenderNode *color_matrix;
|
|
|
|
if (gsk_render_node_get_node_type (transform_child) == GSK_COLOR_MATRIX_NODE)
|
|
{
|
|
color_matrix = merge_color_matrix_nodes (&state->data.color_matrix.matrix,
|
|
&state->data.color_matrix.offset,
|
|
transform_child);
|
|
}
|
|
else
|
|
{
|
|
color_matrix = gsk_color_matrix_node_new (transform_child,
|
|
&state->data.color_matrix.matrix,
|
|
&state->data.color_matrix.offset);
|
|
}
|
|
result = gsk_transform_node_new (color_matrix,
|
|
gsk_transform_node_get_transform (node));
|
|
gsk_render_node_unref (color_matrix);
|
|
gsk_render_node_unref (node);
|
|
node = NULL;
|
|
}
|
|
else
|
|
{
|
|
result = gsk_color_matrix_node_new (node,
|
|
&state->data.color_matrix.matrix,
|
|
&state->data.color_matrix.offset);
|
|
gsk_render_node_unref (node);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_push_color_matrix:
|
|
* @snapshot: a #GtkSnapshot
|
|
* @color_matrix: the color matrix to use
|
|
* @color_offset: the color offset to use
|
|
*
|
|
* Modifies the colors of an image by applying an affine transformation
|
|
* in RGB space.
|
|
*
|
|
* The image is recorded until the next call to gtk_snapshot_pop().
|
|
*/
|
|
void
|
|
gtk_snapshot_push_color_matrix (GtkSnapshot *snapshot,
|
|
const graphene_matrix_t *color_matrix,
|
|
const graphene_vec4_t *color_offset)
|
|
{
|
|
const GtkSnapshotState *current_state = gtk_snapshot_get_current_state (snapshot);
|
|
GtkSnapshotState *state;
|
|
|
|
state = gtk_snapshot_push_state (snapshot,
|
|
current_state->transform,
|
|
gtk_snapshot_collect_color_matrix,
|
|
NULL);
|
|
|
|
graphene_matrix_init_from_matrix (&state->data.color_matrix.matrix, color_matrix);
|
|
graphene_vec4_init_from_vec4 (&state->data.color_matrix.offset, color_offset);
|
|
}
|
|
|
|
static GskRenderNode *
|
|
gtk_snapshot_collect_repeat (GtkSnapshot *snapshot,
|
|
GtkSnapshotState *state,
|
|
GskRenderNode **nodes,
|
|
guint n_nodes)
|
|
{
|
|
GskRenderNode *node, *repeat_node;
|
|
const graphene_rect_t *bounds = &state->data.repeat.bounds;
|
|
const graphene_rect_t *child_bounds = &state->data.repeat.child_bounds;
|
|
|
|
node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
|
|
if (node == NULL)
|
|
return NULL;
|
|
|
|
if (gsk_render_node_get_node_type (node) == GSK_COLOR_NODE &&
|
|
graphene_rect_equal (child_bounds, &node->bounds))
|
|
{
|
|
/* Repeating a color node entirely is pretty easy by just increasing
|
|
* the size of the color node. */
|
|
GskRenderNode *color_node = gsk_color_node_new (gsk_color_node_get_color (node), bounds);
|
|
|
|
gsk_render_node_unref (node);
|
|
|
|
return color_node;
|
|
}
|
|
|
|
repeat_node = gsk_repeat_node_new (bounds,
|
|
node,
|
|
child_bounds->size.width > 0 ? child_bounds : NULL);
|
|
|
|
gsk_render_node_unref (node);
|
|
|
|
return repeat_node;
|
|
}
|
|
|
|
static void
|
|
gtk_graphene_rect_scale_affine (const graphene_rect_t *rect,
|
|
float scale_x,
|
|
float scale_y,
|
|
float dx,
|
|
float dy,
|
|
graphene_rect_t *res)
|
|
{
|
|
res->origin.x = scale_x * rect->origin.x + dx;
|
|
res->origin.y = scale_y * rect->origin.y + dy;
|
|
res->size.width = scale_x * rect->size.width;
|
|
res->size.height = scale_y * rect->size.height;
|
|
|
|
if (scale_x < 0 || scale_y < 0)
|
|
graphene_rect_normalize (res);
|
|
}
|
|
|
|
static void
|
|
gtk_rounded_rect_scale_affine (GskRoundedRect *dest,
|
|
const GskRoundedRect *src,
|
|
float scale_x,
|
|
float scale_y,
|
|
float dx,
|
|
float dy)
|
|
{
|
|
guint flip;
|
|
guint i;
|
|
|
|
g_assert (dest != src);
|
|
|
|
gtk_graphene_rect_scale_affine (&src->bounds, scale_x, scale_y, dx, dy, &dest->bounds);
|
|
flip = ((scale_x < 0) ? 1 : 0) + (scale_y < 0 ? 2 : 0);
|
|
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
dest->corner[i].width = src->corner[i ^ flip].width * fabsf (scale_x);
|
|
dest->corner[i].height = src->corner[i ^ flip].height * fabsf (scale_y);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gtk_snapshot_ensure_affine (GtkSnapshot *snapshot,
|
|
float *scale_x,
|
|
float *scale_y,
|
|
float *dx,
|
|
float *dy)
|
|
{
|
|
const GtkSnapshotState *state = gtk_snapshot_get_current_state (snapshot);
|
|
|
|
if (gsk_transform_get_category (state->transform) < GSK_TRANSFORM_CATEGORY_2D_AFFINE)
|
|
{
|
|
gtk_snapshot_autopush_transform (snapshot);
|
|
state = gtk_snapshot_get_current_state (snapshot);
|
|
gsk_transform_to_affine (state->transform, scale_x, scale_y, dx, dy);
|
|
}
|
|
else if (gsk_transform_get_category (state->transform) == GSK_TRANSFORM_CATEGORY_2D_AFFINE)
|
|
{
|
|
gsk_transform_to_affine (state->transform, scale_x, scale_y, dx, dy);
|
|
if (*scale_x < 0.0 || *scale_y < 0.0)
|
|
{
|
|
gtk_snapshot_autopush_transform (snapshot);
|
|
state = gtk_snapshot_get_current_state (snapshot);
|
|
gsk_transform_to_affine (state->transform, scale_x, scale_y, dx, dy);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gsk_transform_to_affine (state->transform, scale_x, scale_y, dx, dy);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gtk_snapshot_ensure_translate (GtkSnapshot *snapshot,
|
|
float *dx,
|
|
float *dy)
|
|
{
|
|
const GtkSnapshotState *state = gtk_snapshot_get_current_state (snapshot);
|
|
|
|
if (gsk_transform_get_category (state->transform) < GSK_TRANSFORM_CATEGORY_2D_TRANSLATE)
|
|
{
|
|
gtk_snapshot_autopush_transform (snapshot);
|
|
state = gtk_snapshot_get_current_state (snapshot);
|
|
}
|
|
|
|
gsk_transform_to_translate (state->transform, dx, dy);
|
|
}
|
|
|
|
static void
|
|
gtk_snapshot_ensure_identity (GtkSnapshot *snapshot)
|
|
{
|
|
const GtkSnapshotState *state = gtk_snapshot_get_current_state (snapshot);
|
|
|
|
if (gsk_transform_get_category (state->transform) < GSK_TRANSFORM_CATEGORY_IDENTITY)
|
|
gtk_snapshot_autopush_transform (snapshot);
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_push_repeat:
|
|
* @snapshot: a #GtkSnapshot
|
|
* @bounds: the bounds within which to repeat
|
|
* @child_bounds: (nullable): the bounds of the child or %NULL
|
|
* to use the full size of the collected child node
|
|
*
|
|
* Creates a node that repeats the child node.
|
|
*
|
|
* The child is recorded until the next call to gtk_snapshot_pop().
|
|
*/
|
|
void
|
|
gtk_snapshot_push_repeat (GtkSnapshot *snapshot,
|
|
const graphene_rect_t *bounds,
|
|
const graphene_rect_t *child_bounds)
|
|
{
|
|
GtkSnapshotState *state;
|
|
graphene_rect_t real_child_bounds = { { 0 } };
|
|
float scale_x, scale_y, dx, dy;
|
|
|
|
gtk_snapshot_ensure_affine (snapshot, &scale_x, &scale_y, &dx, &dy);
|
|
|
|
if (child_bounds)
|
|
gtk_graphene_rect_scale_affine (child_bounds, scale_x, scale_y, dx, dy, &real_child_bounds);
|
|
|
|
state = gtk_snapshot_push_state (snapshot,
|
|
gtk_snapshot_get_current_state (snapshot)->transform,
|
|
gtk_snapshot_collect_repeat,
|
|
NULL);
|
|
|
|
gtk_graphene_rect_scale_affine (bounds, scale_x, scale_y, dx, dy, &state->data.repeat.bounds);
|
|
state->data.repeat.child_bounds = real_child_bounds;
|
|
}
|
|
|
|
static GskRenderNode *
|
|
gtk_snapshot_collect_clip (GtkSnapshot *snapshot,
|
|
GtkSnapshotState *state,
|
|
GskRenderNode **nodes,
|
|
guint n_nodes)
|
|
{
|
|
GskRenderNode *node, *clip_node;
|
|
|
|
node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
|
|
if (node == NULL)
|
|
return NULL;
|
|
|
|
/* Check if the child node will even be clipped */
|
|
if (graphene_rect_contains_rect (&state->data.clip.bounds, &node->bounds))
|
|
return node;
|
|
|
|
if (state->data.clip.bounds.size.width == 0 ||
|
|
state->data.clip.bounds.size.height == 0)
|
|
return NULL;
|
|
|
|
clip_node = gsk_clip_node_new (node, &state->data.clip.bounds);
|
|
|
|
gsk_render_node_unref (node);
|
|
|
|
return clip_node;
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_push_clip:
|
|
* @snapshot: a #GtkSnapshot
|
|
* @bounds: the rectangle to clip to
|
|
*
|
|
* Clips an image to a rectangle.
|
|
*
|
|
* The image is recorded until the next call to gtk_snapshot_pop().
|
|
*/
|
|
void
|
|
gtk_snapshot_push_clip (GtkSnapshot *snapshot,
|
|
const graphene_rect_t *bounds)
|
|
{
|
|
GtkSnapshotState *state;
|
|
float scale_x, scale_y, dx, dy;
|
|
|
|
gtk_snapshot_ensure_affine (snapshot, &scale_x, &scale_y, &dx, &dy);
|
|
|
|
state = gtk_snapshot_push_state (snapshot,
|
|
gtk_snapshot_get_current_state (snapshot)->transform,
|
|
gtk_snapshot_collect_clip,
|
|
NULL);
|
|
|
|
gtk_graphene_rect_scale_affine (bounds, scale_x, scale_y, dx, dy, &state->data.clip.bounds);
|
|
}
|
|
|
|
static GskRenderNode *
|
|
gtk_snapshot_collect_gl_shader (GtkSnapshot *snapshot,
|
|
GtkSnapshotState *state,
|
|
GskRenderNode **collected_nodes,
|
|
guint n_collected_nodes)
|
|
{
|
|
GskRenderNode *shader_node = NULL;
|
|
GskRenderNode **nodes;
|
|
int n_children;
|
|
|
|
n_children = gsk_gl_shader_get_n_textures (state->data.glshader.shader);
|
|
shader_node = NULL;
|
|
|
|
if (n_collected_nodes != 0)
|
|
g_warning ("Unexpected children when poping gl shader.");
|
|
|
|
if (state->data.glshader.nodes)
|
|
nodes = state->data.glshader.nodes;
|
|
else
|
|
nodes = &state->data.glshader.internal_nodes[0];
|
|
|
|
if (state->data.glshader.bounds.size.width != 0 &&
|
|
state->data.glshader.bounds.size.height != 0)
|
|
shader_node = gsk_gl_shader_node_new (state->data.glshader.shader,
|
|
&state->data.glshader.bounds,
|
|
state->data.glshader.args,
|
|
nodes, n_children);
|
|
|
|
return shader_node;
|
|
}
|
|
|
|
static void
|
|
gtk_snapshot_clear_gl_shader (GtkSnapshotState *state)
|
|
{
|
|
GskRenderNode **nodes;
|
|
guint i, n_children;
|
|
|
|
n_children = gsk_gl_shader_get_n_textures (state->data.glshader.shader);
|
|
|
|
if (state->data.glshader.nodes)
|
|
nodes = state->data.glshader.nodes;
|
|
else
|
|
nodes = &state->data.glshader.internal_nodes[0];
|
|
|
|
g_object_unref (state->data.glshader.shader);
|
|
g_bytes_unref (state->data.glshader.args);
|
|
|
|
for (i = 0; i < n_children; i++)
|
|
gsk_render_node_unref (nodes[i]);
|
|
|
|
g_free (state->data.glshader.nodes);
|
|
|
|
}
|
|
|
|
static GskRenderNode *
|
|
gtk_snapshot_collect_gl_shader_texture (GtkSnapshot *snapshot,
|
|
GtkSnapshotState *state,
|
|
GskRenderNode **nodes,
|
|
guint n_nodes)
|
|
{
|
|
GskRenderNode *child_node;
|
|
GdkRGBA transparent = { 0, 0, 0, 0 };
|
|
int n_children, node_idx;
|
|
GtkSnapshotState *glshader_state;
|
|
GskRenderNode **out_nodes;
|
|
|
|
child_node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
|
|
|
|
if (child_node == NULL)
|
|
child_node = gsk_color_node_new (&transparent, &state->data.glshader_texture.bounds);
|
|
|
|
n_children = state->data.glshader_texture.n_children;
|
|
node_idx = state->data.glshader_texture.node_idx;
|
|
|
|
glshader_state = gtk_snapshot_get_nth_previous_state (snapshot, n_children - node_idx);
|
|
g_assert (glshader_state->collect_func == gtk_snapshot_collect_gl_shader);
|
|
|
|
if (glshader_state->data.glshader.nodes)
|
|
out_nodes = glshader_state->data.glshader.nodes;
|
|
else
|
|
out_nodes = &glshader_state->data.glshader.internal_nodes[0];
|
|
|
|
out_nodes[node_idx] = child_node;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_push_gl_shader:
|
|
* @snapshot: a #GtkSnapshot
|
|
* @shader: The code to run
|
|
* @bounds: the rectangle to render into
|
|
* @take_args: (transfer full): Data block with arguments for the shader.
|
|
*
|
|
* Push a #GskGLShaderNode with a specific #GskGLShader and a set of uniform values
|
|
* to use while rendering. Additionally this takes a list of @n_children other nodes
|
|
* which will be passed to the #GskGLShaderNode.
|
|
*
|
|
* The @take_args argument is a block of data to use for uniform
|
|
* arguments, as per types and offsets defined by the @shader. Normally this is
|
|
* generated by gsk_gl_shader_format_args() or #GskGLShaderArgBuilder.
|
|
* The snapshotter takes ownership of @take_args, so the caller should not free it
|
|
* after this.
|
|
*
|
|
* 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
|
|
* gsk_gl_shader_compile() to ensure the @shader will work for the renderer
|
|
* before using it.
|
|
*
|
|
* If the shader requires textures (see gsk_gl_shader_get_n_textures()), then it is
|
|
* expected that you call gtk_snapshot_gl_shader_pop_texture() the number of times that are
|
|
* required. Each of these calls will generate a node that is added as a child to the gl shader
|
|
* node, which in turn will render these offscreen and pass as a texture to the shader.
|
|
*
|
|
* Once all textures (if any) are pop:ed, you must call the regular gtk_snapshot_pop().
|
|
*
|
|
* If you want to use pre-existing textures as input to the shader rather than
|
|
* rendering new ones, use gtk_snapshot_append_texture() to push a texture node. These
|
|
* will be used directly rather than being re-rendered.
|
|
*
|
|
* For details on how to write shaders, see #GskGLShader.
|
|
*/
|
|
void
|
|
gtk_snapshot_push_gl_shader (GtkSnapshot *snapshot,
|
|
GskGLShader *shader,
|
|
const graphene_rect_t *bounds,
|
|
GBytes *take_args)
|
|
{
|
|
GtkSnapshotState *state;
|
|
float scale_x, scale_y, dx, dy;
|
|
graphene_rect_t transformed_bounds;
|
|
int n_children = gsk_gl_shader_get_n_textures (shader);
|
|
|
|
gtk_snapshot_ensure_affine (snapshot, &scale_x, &scale_y, &dx, &dy);
|
|
|
|
state = gtk_snapshot_push_state (snapshot,
|
|
gtk_snapshot_get_current_state (snapshot)->transform,
|
|
gtk_snapshot_collect_gl_shader,
|
|
gtk_snapshot_clear_gl_shader);
|
|
gtk_graphene_rect_scale_affine (bounds, scale_x, scale_y, dx, dy, &transformed_bounds);
|
|
state->data.glshader.bounds = transformed_bounds;
|
|
state->data.glshader.shader = g_object_ref (shader);
|
|
state->data.glshader.args = take_args; /* Takes ownership */
|
|
if (n_children <= G_N_ELEMENTS (state->data.glshader.internal_nodes))
|
|
state->data.glshader.nodes = NULL;
|
|
else
|
|
state->data.glshader.nodes = g_new (GskRenderNode *, n_children);
|
|
|
|
for (int i = 0; i < n_children; i++)
|
|
{
|
|
state = gtk_snapshot_push_state (snapshot,
|
|
gtk_snapshot_get_current_state (snapshot)->transform,
|
|
gtk_snapshot_collect_gl_shader_texture,
|
|
NULL);
|
|
state->data.glshader_texture.bounds = transformed_bounds;
|
|
state->data.glshader_texture.node_idx = n_children - 1 - i;/* We pop in reverse order */
|
|
state->data.glshader_texture.n_children = n_children;
|
|
}
|
|
}
|
|
|
|
static GskRenderNode *
|
|
gtk_snapshot_collect_rounded_clip (GtkSnapshot *snapshot,
|
|
GtkSnapshotState *state,
|
|
GskRenderNode **nodes,
|
|
guint n_nodes)
|
|
{
|
|
GskRenderNode *node, *clip_node;
|
|
|
|
node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
|
|
if (node == NULL)
|
|
return NULL;
|
|
|
|
/* If the given radius is 0 in all corners, we can just create a normal clip node */
|
|
if (gsk_rounded_rect_is_rectilinear (&state->data.rounded_clip.bounds))
|
|
{
|
|
/* ... and do the same optimization */
|
|
if (graphene_rect_contains_rect (&state->data.rounded_clip.bounds.bounds, &node->bounds))
|
|
return node;
|
|
|
|
clip_node = gsk_clip_node_new (node, &state->data.rounded_clip.bounds.bounds);
|
|
}
|
|
else
|
|
{
|
|
if (gsk_rounded_rect_contains_rect (&state->data.rounded_clip.bounds, &node->bounds))
|
|
return node;
|
|
|
|
clip_node = gsk_rounded_clip_node_new (node, &state->data.rounded_clip.bounds);
|
|
}
|
|
|
|
if (clip_node->bounds.size.width == 0 ||
|
|
clip_node->bounds.size.height == 0)
|
|
{
|
|
gsk_render_node_unref (node);
|
|
gsk_render_node_unref (clip_node);
|
|
return NULL;
|
|
}
|
|
|
|
gsk_render_node_unref (node);
|
|
|
|
return clip_node;
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_push_rounded_clip:
|
|
* @snapshot: a #GtkSnapshot
|
|
* @bounds: the rounded rectangle to clip to
|
|
*
|
|
* Clips an image to a rounded rectangle.
|
|
*
|
|
* The image is recorded until the next call to gtk_snapshot_pop().
|
|
*/
|
|
void
|
|
gtk_snapshot_push_rounded_clip (GtkSnapshot *snapshot,
|
|
const GskRoundedRect *bounds)
|
|
{
|
|
GtkSnapshotState *state;
|
|
float scale_x, scale_y, dx, dy;
|
|
|
|
gtk_snapshot_ensure_affine (snapshot, &scale_x, &scale_y, &dx, &dy);
|
|
|
|
state = gtk_snapshot_push_state (snapshot,
|
|
gtk_snapshot_get_current_state (snapshot)->transform,
|
|
gtk_snapshot_collect_rounded_clip,
|
|
NULL);
|
|
|
|
gtk_rounded_rect_scale_affine (&state->data.rounded_clip.bounds, bounds, scale_x, scale_y, dx, dy);
|
|
}
|
|
|
|
static GskRenderNode *
|
|
gtk_snapshot_collect_shadow (GtkSnapshot *snapshot,
|
|
GtkSnapshotState *state,
|
|
GskRenderNode **nodes,
|
|
guint n_nodes)
|
|
{
|
|
GskRenderNode *node, *shadow_node;
|
|
|
|
node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
|
|
if (node == NULL)
|
|
return NULL;
|
|
|
|
shadow_node = gsk_shadow_node_new (node,
|
|
state->data.shadow.shadows != NULL ?
|
|
state->data.shadow.shadows :
|
|
&state->data.shadow.a_shadow,
|
|
state->data.shadow.n_shadows);
|
|
|
|
gsk_render_node_unref (node);
|
|
|
|
return shadow_node;
|
|
}
|
|
|
|
static void
|
|
gtk_snapshot_clear_shadow (GtkSnapshotState *state)
|
|
{
|
|
g_free (state->data.shadow.shadows);
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_push_shadow:
|
|
* @snapshot: a #GtkSnapshot
|
|
* @shadow: the first shadow specification
|
|
* @n_shadows: number of shadow specifications
|
|
*
|
|
* Applies a shadow to an image.
|
|
*
|
|
* The image is recorded until the next call to gtk_snapshot_pop().
|
|
*/
|
|
void
|
|
gtk_snapshot_push_shadow (GtkSnapshot *snapshot,
|
|
const GskShadow *shadow,
|
|
gsize n_shadows)
|
|
{
|
|
const GtkSnapshotState *current_state = gtk_snapshot_get_current_state (snapshot);
|
|
GtkSnapshotState *state;
|
|
|
|
state = gtk_snapshot_push_state (snapshot,
|
|
current_state->transform,
|
|
gtk_snapshot_collect_shadow,
|
|
gtk_snapshot_clear_shadow);
|
|
|
|
state->data.shadow.n_shadows = n_shadows;
|
|
if (n_shadows == 1)
|
|
{
|
|
state->data.shadow.shadows = NULL;
|
|
memcpy (&state->data.shadow.a_shadow, shadow, sizeof (GskShadow));
|
|
}
|
|
else
|
|
{
|
|
state->data.shadow.shadows = g_malloc (sizeof (GskShadow) * n_shadows);
|
|
memcpy (state->data.shadow.shadows, shadow, sizeof (GskShadow) * n_shadows);
|
|
}
|
|
|
|
}
|
|
|
|
static GskRenderNode *
|
|
gtk_snapshot_collect_blend_top (GtkSnapshot *snapshot,
|
|
GtkSnapshotState *state,
|
|
GskRenderNode **nodes,
|
|
guint n_nodes)
|
|
{
|
|
GskRenderNode *bottom_node, *top_node, *blend_node;
|
|
GdkRGBA transparent = { 0, 0, 0, 0 };
|
|
|
|
top_node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
|
|
bottom_node = state->data.blend.bottom_node != NULL
|
|
? gsk_render_node_ref (state->data.blend.bottom_node)
|
|
: NULL;
|
|
|
|
g_assert (top_node != NULL || bottom_node != NULL);
|
|
|
|
/* XXX: Is this necessary? Do we need a NULL node? */
|
|
if (top_node == NULL)
|
|
top_node = gsk_color_node_new (&transparent, &bottom_node->bounds);
|
|
if (bottom_node == NULL)
|
|
bottom_node = gsk_color_node_new (&transparent, &top_node->bounds);
|
|
|
|
blend_node = gsk_blend_node_new (bottom_node, top_node, state->data.blend.blend_mode);
|
|
|
|
gsk_render_node_unref (top_node);
|
|
gsk_render_node_unref (bottom_node);
|
|
|
|
return blend_node;
|
|
}
|
|
|
|
static void
|
|
gtk_snapshot_clear_blend_top (GtkSnapshotState *state)
|
|
{
|
|
g_clear_pointer (&(state->data.blend.bottom_node), gsk_render_node_unref);
|
|
}
|
|
|
|
static GskRenderNode *
|
|
gtk_snapshot_collect_blend_bottom (GtkSnapshot *snapshot,
|
|
GtkSnapshotState *state,
|
|
GskRenderNode **nodes,
|
|
guint n_nodes)
|
|
{
|
|
GtkSnapshotState *prev_state = gtk_snapshot_get_previous_state (snapshot);
|
|
|
|
g_assert (prev_state->collect_func == gtk_snapshot_collect_blend_top);
|
|
|
|
prev_state->data.blend.bottom_node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_push_blend:
|
|
* @snapshot: a #GtkSnapshot
|
|
* @blend_mode: blend mode to use
|
|
*
|
|
* Blends together 2 images with the given blend mode.
|
|
*
|
|
* Until the first call to gtk_snapshot_pop(), the bottom image for the
|
|
* blend operation will be recorded. After that call, the top image to
|
|
* be blended will be recorded until the second call to gtk_snapshot_pop().
|
|
*
|
|
* Calling this function requires 2 subsequent calls to gtk_snapshot_pop().
|
|
**/
|
|
void
|
|
gtk_snapshot_push_blend (GtkSnapshot *snapshot,
|
|
GskBlendMode blend_mode)
|
|
{
|
|
GtkSnapshotState *current_state = gtk_snapshot_get_current_state (snapshot);
|
|
GtkSnapshotState *top_state;
|
|
|
|
top_state = gtk_snapshot_push_state (snapshot,
|
|
current_state->transform,
|
|
gtk_snapshot_collect_blend_top,
|
|
gtk_snapshot_clear_blend_top);
|
|
top_state->data.blend.blend_mode = blend_mode;
|
|
|
|
gtk_snapshot_push_state (snapshot,
|
|
top_state->transform,
|
|
gtk_snapshot_collect_blend_bottom,
|
|
NULL);
|
|
}
|
|
|
|
static GskRenderNode *
|
|
gtk_snapshot_collect_cross_fade_end (GtkSnapshot *snapshot,
|
|
GtkSnapshotState *state,
|
|
GskRenderNode **nodes,
|
|
guint n_nodes)
|
|
{
|
|
GskRenderNode *start_node, *end_node, *node;
|
|
|
|
end_node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
|
|
start_node = state->data.cross_fade.start_node;
|
|
state->data.cross_fade.start_node = NULL;
|
|
|
|
if (state->data.cross_fade.progress <= 0.0)
|
|
{
|
|
node = start_node;
|
|
|
|
if (end_node)
|
|
gsk_render_node_unref (end_node);
|
|
}
|
|
else if (state->data.cross_fade.progress >= 1.0)
|
|
{
|
|
node = end_node;
|
|
|
|
if (start_node)
|
|
gsk_render_node_unref (start_node);
|
|
}
|
|
else if (start_node && end_node)
|
|
{
|
|
node = gsk_cross_fade_node_new (start_node, end_node, state->data.cross_fade.progress);
|
|
|
|
gsk_render_node_unref (start_node);
|
|
gsk_render_node_unref (end_node);
|
|
}
|
|
else if (start_node)
|
|
{
|
|
node = gsk_opacity_node_new (start_node, 1.0 - state->data.cross_fade.progress);
|
|
|
|
gsk_render_node_unref (start_node);
|
|
}
|
|
else if (end_node)
|
|
{
|
|
node = gsk_opacity_node_new (end_node, state->data.cross_fade.progress);
|
|
|
|
gsk_render_node_unref (end_node);
|
|
}
|
|
else
|
|
{
|
|
node = NULL;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
static void
|
|
gtk_snapshot_clear_cross_fade_end (GtkSnapshotState *state)
|
|
{
|
|
g_clear_pointer (&state->data.cross_fade.start_node, gsk_render_node_unref);
|
|
}
|
|
|
|
static GskRenderNode *
|
|
gtk_snapshot_collect_cross_fade_start (GtkSnapshot *snapshot,
|
|
GtkSnapshotState *state,
|
|
GskRenderNode **nodes,
|
|
guint n_nodes)
|
|
{
|
|
GtkSnapshotState *prev_state = gtk_snapshot_get_previous_state (snapshot);
|
|
|
|
g_assert (prev_state->collect_func == gtk_snapshot_collect_cross_fade_end);
|
|
|
|
prev_state->data.cross_fade.start_node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_push_cross_fade:
|
|
* @snapshot: a #GtkSnapshot
|
|
* @progress: progress between 0.0 and 1.0
|
|
*
|
|
* Snapshots a cross-fade operation between two images with the
|
|
* given @progress.
|
|
*
|
|
* Until the first call to gtk_snapshot_pop(), the start image
|
|
* will be snapshot. After that call, the end image will be recorded
|
|
* until the second call to gtk_snapshot_pop().
|
|
*
|
|
* Calling this function requires 2 calls to gtk_snapshot_pop().
|
|
**/
|
|
void
|
|
gtk_snapshot_push_cross_fade (GtkSnapshot *snapshot,
|
|
double progress)
|
|
{
|
|
const GtkSnapshotState *current_state = gtk_snapshot_get_current_state (snapshot);
|
|
GtkSnapshotState *end_state;
|
|
|
|
end_state = gtk_snapshot_push_state (snapshot,
|
|
current_state->transform,
|
|
gtk_snapshot_collect_cross_fade_end,
|
|
gtk_snapshot_clear_cross_fade_end);
|
|
end_state->data.cross_fade.progress = progress;
|
|
|
|
gtk_snapshot_push_state (snapshot,
|
|
end_state->transform,
|
|
gtk_snapshot_collect_cross_fade_start,
|
|
NULL);
|
|
}
|
|
|
|
static GskRenderNode *
|
|
gtk_snapshot_pop_one (GtkSnapshot *snapshot)
|
|
{
|
|
GtkSnapshotState *state;
|
|
guint state_index;
|
|
GskRenderNode *node;
|
|
|
|
if (gtk_snapshot_states_is_empty (&snapshot->state_stack))
|
|
{
|
|
g_warning ("Too many gtk_snapshot_pop() calls.");
|
|
return NULL;
|
|
}
|
|
|
|
state = gtk_snapshot_get_current_state (snapshot);
|
|
state_index = gtk_snapshot_states_get_size (&snapshot->state_stack) - 1;
|
|
|
|
if (state->collect_func)
|
|
{
|
|
node = state->collect_func (snapshot,
|
|
state,
|
|
(GskRenderNode **) gtk_snapshot_nodes_index (&snapshot->nodes, state->start_node_index),
|
|
state->n_nodes);
|
|
|
|
/* The collect func may not modify the state stack... */
|
|
g_assert (state_index == gtk_snapshot_states_get_size (&snapshot->state_stack) - 1);
|
|
|
|
/* Remove all the state's nodes from the list of nodes */
|
|
g_assert (state->start_node_index + state->n_nodes == gtk_snapshot_nodes_get_size (&snapshot->nodes));
|
|
gtk_snapshot_nodes_splice (&snapshot->nodes, state->start_node_index, state->n_nodes, NULL, 0);
|
|
}
|
|
else
|
|
{
|
|
GtkSnapshotState *previous_state;
|
|
|
|
node = NULL;
|
|
|
|
/* move the nodes to the parent */
|
|
previous_state = gtk_snapshot_get_previous_state (snapshot);
|
|
previous_state->n_nodes += state->n_nodes;
|
|
g_assert (previous_state->start_node_index + previous_state->n_nodes == gtk_snapshot_nodes_get_size (&snapshot->nodes));
|
|
}
|
|
|
|
gtk_snapshot_states_splice (&snapshot->state_stack, state_index, 1, NULL, 0);
|
|
|
|
return node;
|
|
}
|
|
|
|
static void
|
|
gtk_snapshot_append_node_internal (GtkSnapshot *snapshot,
|
|
GskRenderNode *node)
|
|
{
|
|
GtkSnapshotState *current_state;
|
|
|
|
current_state = gtk_snapshot_get_current_state (snapshot);
|
|
|
|
if (current_state)
|
|
{
|
|
gtk_snapshot_nodes_append (&snapshot->nodes, node);
|
|
current_state->n_nodes ++;
|
|
}
|
|
else
|
|
{
|
|
g_critical ("Tried appending a node to an already finished snapshot.");
|
|
}
|
|
}
|
|
|
|
static GskRenderNode *
|
|
gtk_snapshot_pop_internal (GtkSnapshot *snapshot)
|
|
{
|
|
GtkSnapshotState *state;
|
|
GskRenderNode *node;
|
|
guint forgotten_restores = 0;
|
|
|
|
for (state = gtk_snapshot_get_current_state (snapshot);
|
|
gtk_snapshot_state_should_autopop (state) ||
|
|
state->collect_func == NULL;
|
|
state = gtk_snapshot_get_current_state (snapshot))
|
|
{
|
|
if (state->collect_func == NULL)
|
|
forgotten_restores++;
|
|
|
|
node = gtk_snapshot_pop_one (snapshot);
|
|
if (node)
|
|
gtk_snapshot_append_node_internal (snapshot, node);
|
|
}
|
|
|
|
if (forgotten_restores)
|
|
{
|
|
g_warning ("Too many gtk_snapshot_save() calls. %u saves remaining.", forgotten_restores);
|
|
}
|
|
|
|
return gtk_snapshot_pop_one (snapshot);
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_push_collect:
|
|
*
|
|
* PRIVATE.
|
|
*
|
|
* Puhses state so a later pop_collect call can collect all nodes
|
|
* appended until that point.
|
|
*/
|
|
void
|
|
gtk_snapshot_push_collect (GtkSnapshot *snapshot)
|
|
{
|
|
gtk_snapshot_push_state (snapshot,
|
|
NULL,
|
|
gtk_snapshot_collect_default,
|
|
NULL);
|
|
}
|
|
|
|
GskRenderNode *
|
|
gtk_snapshot_pop_collect (GtkSnapshot *snapshot)
|
|
{
|
|
GskRenderNode *result = gtk_snapshot_pop_internal (snapshot);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_to_node:
|
|
* @snapshot: a #GtkSnapshot
|
|
*
|
|
* Returns the render node that was constructed
|
|
* by @snapshot. After calling this function, it
|
|
* is no longer possible to add more nodes to
|
|
* @snapshot. The only function that should be
|
|
* called after this is g_object_unref().
|
|
*
|
|
* Returns: (transfer full): the constructed #GskRenderNode
|
|
*/
|
|
GskRenderNode *
|
|
gtk_snapshot_to_node (GtkSnapshot *snapshot)
|
|
{
|
|
GskRenderNode *result;
|
|
|
|
result = gtk_snapshot_pop_internal (snapshot);
|
|
|
|
/* We should have exactly our initial state */
|
|
if (!gtk_snapshot_states_is_empty (&snapshot->state_stack))
|
|
{
|
|
g_warning ("Too many gtk_snapshot_push() calls. %zu states remaining.",
|
|
gtk_snapshot_states_get_size (&snapshot->state_stack));
|
|
}
|
|
|
|
gtk_snapshot_states_clear (&snapshot->state_stack);
|
|
gtk_snapshot_nodes_clear (&snapshot->nodes);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_to_paintable:
|
|
* @snapshot: a #GtkSnapshot
|
|
* @size: (allow-none): The size of the resulting paintable
|
|
* or %NULL to use the bounds of the snapshot
|
|
*
|
|
* Returns a paintable encapsulating the render node
|
|
* that was constructed by @snapshot. After calling
|
|
* this function, it is no longer possible to add more
|
|
* nodes to @snapshot. The only function that should be
|
|
* called after this is g_object_unref().
|
|
*
|
|
* Returns: (transfer full): a new #GdkPaintable
|
|
*/
|
|
GdkPaintable *
|
|
gtk_snapshot_to_paintable (GtkSnapshot *snapshot,
|
|
const graphene_size_t *size)
|
|
{
|
|
GskRenderNode *node;
|
|
GdkPaintable *paintable;
|
|
graphene_rect_t bounds;
|
|
|
|
node = gtk_snapshot_to_node (snapshot);
|
|
if (size)
|
|
{
|
|
graphene_size_init_from_size (&bounds.size, size);
|
|
}
|
|
else
|
|
{
|
|
gsk_render_node_get_bounds (node, &bounds);
|
|
bounds.size.width += bounds.origin.x;
|
|
bounds.size.height += bounds.origin.y;
|
|
}
|
|
bounds.origin.x = 0;
|
|
bounds.origin.y = 0;
|
|
|
|
paintable = gtk_render_node_paintable_new (node, &bounds);
|
|
gsk_render_node_unref (node);
|
|
|
|
return paintable;
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_pop:
|
|
* @snapshot: a #GtkSnapshot
|
|
*
|
|
* Removes the top element from the stack of render nodes,
|
|
* and appends it to the node underneath it.
|
|
*/
|
|
void
|
|
gtk_snapshot_pop (GtkSnapshot *snapshot)
|
|
{
|
|
GtkSnapshotState *state = gtk_snapshot_get_current_state (snapshot);
|
|
GskRenderNode *node;
|
|
|
|
if (state->collect_func == gtk_snapshot_collect_gl_shader_texture)
|
|
g_warning ("Not enough calls to gtk_snapshot_gl_shader_pop_texture().");
|
|
|
|
node = gtk_snapshot_pop_internal (snapshot);
|
|
|
|
if (node)
|
|
gtk_snapshot_append_node_internal (snapshot, node);
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_gl_shader_pop_texture:
|
|
* @snapshot: a #GtkSnapshot
|
|
*
|
|
* Removes the top element from the stack of render nodes and
|
|
* adds it to the nearest GskGLShaderNode below it. This must be called the
|
|
* same number of times as the number of textures is needed for the
|
|
* shader in gtk_snapshot_push_gl_shader().
|
|
*/
|
|
void
|
|
gtk_snapshot_gl_shader_pop_texture (GtkSnapshot *snapshot)
|
|
{
|
|
GtkSnapshotState *state = gtk_snapshot_get_current_state (snapshot);
|
|
GskRenderNode *node;
|
|
|
|
if (state->collect_func != gtk_snapshot_collect_gl_shader_texture)
|
|
{
|
|
g_warning ("Too many calls to gtk_snapshot_gl_shader_pop_texture().");
|
|
return;
|
|
}
|
|
|
|
g_assert (state->collect_func == gtk_snapshot_collect_gl_shader_texture);
|
|
|
|
node = gtk_snapshot_pop_internal (snapshot);
|
|
g_assert (node == NULL);
|
|
}
|
|
|
|
|
|
/**
|
|
* gtk_snapshot_save:
|
|
* @snapshot: a #GtkSnapshot
|
|
*
|
|
* Makes a copy of the current state of @snapshot and saves it
|
|
* on an internal stack of saved states for @snapshot. When
|
|
* gtk_snapshot_restore() is called, @snapshot will be restored to
|
|
* the saved state. Multiple calls to gtk_snapshot_save() and
|
|
* gtk_snapshot_restore() can be nested; each call to
|
|
* gtk_snapshot_restore() restores the state from the matching paired
|
|
* gtk_snapshot_save().
|
|
*
|
|
* It is necessary to clear all saved states with corresponding calls
|
|
* to gtk_snapshot_restore().
|
|
**/
|
|
void
|
|
gtk_snapshot_save (GtkSnapshot *snapshot)
|
|
{
|
|
g_return_if_fail (GTK_IS_SNAPSHOT (snapshot));
|
|
|
|
gtk_snapshot_push_state (snapshot,
|
|
gtk_snapshot_get_current_state (snapshot)->transform,
|
|
NULL,
|
|
NULL);
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_restore:
|
|
* @snapshot: a #GtkSnapshot
|
|
*
|
|
* Restores @snapshot to the state saved by a preceding call to
|
|
* gtk_snapshot_save() and removes that state from the stack of
|
|
* saved states.
|
|
**/
|
|
void
|
|
gtk_snapshot_restore (GtkSnapshot *snapshot)
|
|
{
|
|
GtkSnapshotState *state;
|
|
GskRenderNode *node;
|
|
|
|
for (state = gtk_snapshot_get_current_state (snapshot);
|
|
gtk_snapshot_state_should_autopop (state);
|
|
state = gtk_snapshot_get_current_state (snapshot))
|
|
{
|
|
node = gtk_snapshot_pop_one (snapshot);
|
|
if (node)
|
|
gtk_snapshot_append_node_internal (snapshot, node);
|
|
}
|
|
|
|
if (state->collect_func != NULL)
|
|
{
|
|
g_warning ("Too many gtk_snapshot_restore() calls.");
|
|
return;
|
|
}
|
|
|
|
node = gtk_snapshot_pop_one (snapshot);
|
|
g_assert (node == NULL);
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_transform:
|
|
* @snapshot: a #GtkSnapshot
|
|
* @transform: (allow-none): the transform to apply
|
|
*
|
|
* Transforms @snapshot's coordinate system with the given @transform.
|
|
**/
|
|
void
|
|
gtk_snapshot_transform (GtkSnapshot *snapshot,
|
|
GskTransform *transform)
|
|
{
|
|
GtkSnapshotState *state;
|
|
|
|
g_return_if_fail (GTK_IS_SNAPSHOT (snapshot));
|
|
|
|
state = gtk_snapshot_get_current_state (snapshot);
|
|
state->transform = gsk_transform_transform (state->transform, transform);
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_transform_matrix:
|
|
* @snapshot: a #GtkSnapshot
|
|
* @matrix: the matrix to multiply the transform with
|
|
*
|
|
* Transforms @snapshot's coordinate system with the given @matrix.
|
|
**/
|
|
void
|
|
gtk_snapshot_transform_matrix (GtkSnapshot *snapshot,
|
|
const graphene_matrix_t *matrix)
|
|
{
|
|
GtkSnapshotState *state;
|
|
|
|
g_return_if_fail (GTK_IS_SNAPSHOT (snapshot));
|
|
g_return_if_fail (matrix != NULL);
|
|
|
|
state = gtk_snapshot_get_current_state (snapshot);
|
|
state->transform = gsk_transform_matrix (state->transform, matrix);
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_translate:
|
|
* @snapshot: a #GtkSnapshot
|
|
* @point: the point to translate the snapshot by
|
|
*
|
|
* Translates @snapshot's coordinate system by @point in 2-dimensional space.
|
|
*/
|
|
void
|
|
gtk_snapshot_translate (GtkSnapshot *snapshot,
|
|
const graphene_point_t *point)
|
|
{
|
|
GtkSnapshotState *state;
|
|
|
|
g_return_if_fail (GTK_IS_SNAPSHOT (snapshot));
|
|
g_return_if_fail (point != NULL);
|
|
|
|
state = gtk_snapshot_get_current_state (snapshot);
|
|
state->transform = gsk_transform_translate (state->transform, point);
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_translate_3d:
|
|
* @snapshot: a #GtkSnapshot
|
|
* @point: the point to translate the snapshot by
|
|
*
|
|
* Translates @snapshot's coordinate system by @point.
|
|
*/
|
|
void
|
|
gtk_snapshot_translate_3d (GtkSnapshot *snapshot,
|
|
const graphene_point3d_t *point)
|
|
{
|
|
GtkSnapshotState *state;
|
|
|
|
g_return_if_fail (GTK_IS_SNAPSHOT (snapshot));
|
|
g_return_if_fail (point != NULL);
|
|
|
|
state = gtk_snapshot_get_current_state (snapshot);
|
|
state->transform = gsk_transform_translate_3d (state->transform, point);
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_rotate:
|
|
* @snapshot: a #GtkSnapshot
|
|
* @angle: the rotation angle, in degrees (clockwise)
|
|
*
|
|
* Rotates @@snapshot's coordinate system by @angle degrees in 2D space -
|
|
* or in 3D speak, rotates around the z axis.
|
|
*/
|
|
void
|
|
gtk_snapshot_rotate (GtkSnapshot *snapshot,
|
|
float angle)
|
|
{
|
|
GtkSnapshotState *state;
|
|
|
|
g_return_if_fail (GTK_IS_SNAPSHOT (snapshot));
|
|
|
|
state = gtk_snapshot_get_current_state (snapshot);
|
|
state->transform = gsk_transform_rotate (state->transform, angle);
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_rotate_3d:
|
|
* @snapshot: a #GtkSnapshot
|
|
* @angle: the rotation angle, in degrees (clockwise)
|
|
* @axis: The rotation axis
|
|
*
|
|
* Rotates @snapshot's coordinate system by @angle degrees around @axis.
|
|
*
|
|
* For a rotation in 2D space, use gsk_transform_rotate().
|
|
*/
|
|
void
|
|
gtk_snapshot_rotate_3d (GtkSnapshot *snapshot,
|
|
float angle,
|
|
const graphene_vec3_t *axis)
|
|
{
|
|
GtkSnapshotState *state;
|
|
|
|
g_return_if_fail (GTK_IS_SNAPSHOT (snapshot));
|
|
g_return_if_fail (axis != NULL);
|
|
|
|
state = gtk_snapshot_get_current_state (snapshot);
|
|
state->transform = gsk_transform_rotate_3d (state->transform, angle, axis);
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_scale:
|
|
* @snapshot: a #GtkSnapshot
|
|
* @factor_x: scaling factor on the X axis
|
|
* @factor_y: scaling factor on the Y axis
|
|
*
|
|
* Scales @snapshot's coordinate system in 2-dimensional space by
|
|
* the given factors.
|
|
*
|
|
* Use gtk_snapshot_scale_3d() to scale in all 3 dimensions.
|
|
**/
|
|
void
|
|
gtk_snapshot_scale (GtkSnapshot *snapshot,
|
|
float factor_x,
|
|
float factor_y)
|
|
{
|
|
GtkSnapshotState *state;
|
|
|
|
g_return_if_fail (GTK_IS_SNAPSHOT (snapshot));
|
|
|
|
state = gtk_snapshot_get_current_state (snapshot);
|
|
state->transform = gsk_transform_scale (state->transform, factor_x, factor_y);
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_scale_3d:
|
|
* @snapshot: a #GtkSnapshot
|
|
* @factor_x: scaling factor on the X axis
|
|
* @factor_y: scaling factor on the Y axis
|
|
* @factor_z: scaling factor on the Z axis
|
|
*
|
|
* Scales @snapshot's coordinate system by the given factors.
|
|
*/
|
|
void
|
|
gtk_snapshot_scale_3d (GtkSnapshot *snapshot,
|
|
float factor_x,
|
|
float factor_y,
|
|
float factor_z)
|
|
{
|
|
GtkSnapshotState *state;
|
|
|
|
g_return_if_fail (GTK_IS_SNAPSHOT (snapshot));
|
|
|
|
state = gtk_snapshot_get_current_state (snapshot);
|
|
state->transform = gsk_transform_scale_3d (state->transform, factor_x, factor_y, factor_z);
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_perspective:
|
|
* @snapshot: a #GtkSnapshot
|
|
* @depth: distance of the z=0 plane
|
|
*
|
|
* Applies a perspective projection transform.
|
|
*
|
|
* See gsk_transform_perspective() for a discussion on the details.
|
|
*/
|
|
void
|
|
gtk_snapshot_perspective (GtkSnapshot *snapshot,
|
|
float depth)
|
|
{
|
|
GtkSnapshotState *state;
|
|
|
|
g_return_if_fail (GTK_IS_SNAPSHOT (snapshot));
|
|
|
|
state = gtk_snapshot_get_current_state (snapshot);
|
|
state->transform = gsk_transform_perspective (state->transform, depth);
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_append_node:
|
|
* @snapshot: a #GtkSnapshot
|
|
* @node: a #GskRenderNode
|
|
*
|
|
* Appends @node to the current render node of @snapshot,
|
|
* without changing the current node. If @snapshot does
|
|
* not have a current node yet, @node will become the
|
|
* initial node.
|
|
*/
|
|
void
|
|
gtk_snapshot_append_node (GtkSnapshot *snapshot,
|
|
GskRenderNode *node)
|
|
{
|
|
g_return_if_fail (snapshot != NULL);
|
|
g_return_if_fail (GSK_IS_RENDER_NODE (node));
|
|
|
|
gtk_snapshot_ensure_identity (snapshot);
|
|
|
|
gtk_snapshot_append_node_internal (snapshot, gsk_render_node_ref (node));
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_append_cairo:
|
|
* @snapshot: a #GtkSnapshot
|
|
* @bounds: the bounds for the new node
|
|
*
|
|
* Creates a new render node and appends it to the current render
|
|
* node of @snapshot, without changing the current node.
|
|
*
|
|
* Returns: a cairo_t suitable for drawing the contents of the newly
|
|
* created render node
|
|
*/
|
|
cairo_t *
|
|
gtk_snapshot_append_cairo (GtkSnapshot *snapshot,
|
|
const graphene_rect_t *bounds)
|
|
{
|
|
GskRenderNode *node;
|
|
graphene_rect_t real_bounds;
|
|
float scale_x, scale_y, dx, dy;
|
|
cairo_t *cr;
|
|
|
|
g_return_val_if_fail (snapshot != NULL, NULL);
|
|
g_return_val_if_fail (bounds != NULL, NULL);
|
|
|
|
gtk_snapshot_ensure_affine (snapshot, &scale_x, &scale_y, &dx, &dy);
|
|
gtk_graphene_rect_scale_affine (bounds, scale_x, scale_y, dx, dy, &real_bounds);
|
|
|
|
node = gsk_cairo_node_new (&real_bounds);
|
|
|
|
gtk_snapshot_append_node_internal (snapshot, node);
|
|
|
|
cr = gsk_cairo_node_get_draw_context (node);
|
|
|
|
cairo_scale (cr, scale_x, scale_y);
|
|
cairo_translate (cr, dx, dy);
|
|
|
|
return cr;
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_append_texture:
|
|
* @snapshot: a #GtkSnapshot
|
|
* @texture: the #GdkTexture to render
|
|
* @bounds: the bounds for the new node
|
|
*
|
|
* Creates a new render node drawing the @texture into the given @bounds and appends it
|
|
* to the current render node of @snapshot.
|
|
**/
|
|
void
|
|
gtk_snapshot_append_texture (GtkSnapshot *snapshot,
|
|
GdkTexture *texture,
|
|
const graphene_rect_t *bounds)
|
|
{
|
|
GskRenderNode *node;
|
|
graphene_rect_t real_bounds;
|
|
float scale_x, scale_y, dx, dy;
|
|
|
|
g_return_if_fail (snapshot != NULL);
|
|
g_return_if_fail (GDK_IS_TEXTURE (texture));
|
|
g_return_if_fail (bounds != NULL);
|
|
|
|
gtk_snapshot_ensure_affine (snapshot, &scale_x, &scale_y, &dx, &dy);
|
|
gtk_graphene_rect_scale_affine (bounds, scale_x, scale_y, dx, dy, &real_bounds);
|
|
node = gsk_texture_node_new (texture, &real_bounds);
|
|
|
|
gtk_snapshot_append_node_internal (snapshot, node);
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_append_color:
|
|
* @snapshot: a #GtkSnapshot
|
|
* @color: the #GdkRGBA to draw
|
|
* @bounds: the bounds for the new node
|
|
*
|
|
* Creates a new render node drawing the @color into the given @bounds and appends it
|
|
* to the current render node of @snapshot.
|
|
*
|
|
* You should try to avoid calling this function if @color is transparent.
|
|
**/
|
|
void
|
|
gtk_snapshot_append_color (GtkSnapshot *snapshot,
|
|
const GdkRGBA *color,
|
|
const graphene_rect_t *bounds)
|
|
{
|
|
GskRenderNode *node;
|
|
graphene_rect_t real_bounds;
|
|
float scale_x, scale_y, dx, dy;
|
|
|
|
g_return_if_fail (snapshot != NULL);
|
|
g_return_if_fail (color != NULL);
|
|
g_return_if_fail (bounds != NULL);
|
|
|
|
gtk_snapshot_ensure_affine (snapshot, &scale_x, &scale_y, &dx, &dy);
|
|
gtk_graphene_rect_scale_affine (bounds, scale_x, scale_y, dx, dy, &real_bounds);
|
|
|
|
node = gsk_color_node_new (color, &real_bounds);
|
|
|
|
gtk_snapshot_append_node_internal (snapshot, node);
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_render_background:
|
|
* @snapshot: a #GtkSnapshot
|
|
* @context: the #GtkStyleContext to use
|
|
* @x: X origin of the rectangle
|
|
* @y: Y origin of the rectangle
|
|
* @width: rectangle width
|
|
* @height: rectangle height
|
|
*
|
|
* Creates a render node for the CSS background according to @context,
|
|
* and appends it to the current node of @snapshot, without changing
|
|
* the current node.
|
|
*/
|
|
void
|
|
gtk_snapshot_render_background (GtkSnapshot *snapshot,
|
|
GtkStyleContext *context,
|
|
double x,
|
|
double y,
|
|
double width,
|
|
double height)
|
|
{
|
|
GtkCssBoxes boxes;
|
|
|
|
g_return_if_fail (snapshot != NULL);
|
|
g_return_if_fail (GTK_IS_STYLE_CONTEXT (context));
|
|
|
|
gtk_css_boxes_init_border_box (&boxes,
|
|
gtk_style_context_lookup_style (context),
|
|
x, y, width, height);
|
|
gtk_css_style_snapshot_background (&boxes, snapshot);
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_render_frame:
|
|
* @snapshot: a #GtkSnapshot
|
|
* @context: the #GtkStyleContext to use
|
|
* @x: X origin of the rectangle
|
|
* @y: Y origin of the rectangle
|
|
* @width: rectangle width
|
|
* @height: rectangle height
|
|
*
|
|
* Creates a render node for the CSS border according to @context,
|
|
* and appends it to the current node of @snapshot, without changing
|
|
* the current node.
|
|
*/
|
|
void
|
|
gtk_snapshot_render_frame (GtkSnapshot *snapshot,
|
|
GtkStyleContext *context,
|
|
double x,
|
|
double y,
|
|
double width,
|
|
double height)
|
|
{
|
|
GtkCssBoxes boxes;
|
|
|
|
g_return_if_fail (snapshot != NULL);
|
|
g_return_if_fail (GTK_IS_STYLE_CONTEXT (context));
|
|
|
|
gtk_css_boxes_init_border_box (&boxes,
|
|
gtk_style_context_lookup_style (context),
|
|
x, y, width, height);
|
|
gtk_css_style_snapshot_border (&boxes, snapshot);
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_render_focus:
|
|
* @snapshot: a #GtkSnapshot
|
|
* @context: the #GtkStyleContext to use
|
|
* @x: X origin of the rectangle
|
|
* @y: Y origin of the rectangle
|
|
* @width: rectangle width
|
|
* @height: rectangle height
|
|
*
|
|
* Creates a render node for the focus outline according to @context,
|
|
* and appends it to the current node of @snapshot, without changing
|
|
* the current node.
|
|
*/
|
|
void
|
|
gtk_snapshot_render_focus (GtkSnapshot *snapshot,
|
|
GtkStyleContext *context,
|
|
double x,
|
|
double y,
|
|
double width,
|
|
double height)
|
|
{
|
|
GtkCssBoxes boxes;
|
|
|
|
g_return_if_fail (snapshot != NULL);
|
|
g_return_if_fail (GTK_IS_STYLE_CONTEXT (context));
|
|
|
|
gtk_css_boxes_init_border_box (&boxes,
|
|
gtk_style_context_lookup_style (context),
|
|
x, y, width, height);
|
|
gtk_css_style_snapshot_outline (&boxes, snapshot);
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_render_layout:
|
|
* @snapshot: a #GtkSnapshot
|
|
* @context: the #GtkStyleContext to use
|
|
* @x: X origin of the rectangle
|
|
* @y: Y origin of the rectangle
|
|
* @layout: the #PangoLayout to render
|
|
*
|
|
* Creates a render node for rendering @layout according to the style
|
|
* information in @context, and appends it to the current node of @snapshot,
|
|
* without changing the current node.
|
|
*/
|
|
void
|
|
gtk_snapshot_render_layout (GtkSnapshot *snapshot,
|
|
GtkStyleContext *context,
|
|
double x,
|
|
double y,
|
|
PangoLayout *layout)
|
|
{
|
|
const bool needs_translate = (x != 0 || y != 0);
|
|
const GdkRGBA *fg_color;
|
|
GtkCssValue *shadows_value;
|
|
gboolean has_shadow;
|
|
|
|
g_return_if_fail (snapshot != NULL);
|
|
g_return_if_fail (GTK_IS_STYLE_CONTEXT (context));
|
|
g_return_if_fail (PANGO_IS_LAYOUT (layout));
|
|
|
|
if (needs_translate)
|
|
{
|
|
gtk_snapshot_save (snapshot);
|
|
gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (x, y));
|
|
}
|
|
|
|
fg_color = gtk_css_color_value_get_rgba (_gtk_style_context_peek_property (context, GTK_CSS_PROPERTY_COLOR));
|
|
|
|
shadows_value = _gtk_style_context_peek_property (context, GTK_CSS_PROPERTY_TEXT_SHADOW);
|
|
has_shadow = gtk_css_shadow_value_push_snapshot (shadows_value, snapshot);
|
|
|
|
gtk_snapshot_append_layout (snapshot, layout, fg_color);
|
|
|
|
if (has_shadow)
|
|
gtk_snapshot_pop (snapshot);
|
|
|
|
if (needs_translate)
|
|
gtk_snapshot_restore (snapshot);
|
|
}
|
|
|
|
void
|
|
gtk_snapshot_append_text (GtkSnapshot *snapshot,
|
|
PangoFont *font,
|
|
PangoGlyphString *glyphs,
|
|
const GdkRGBA *color,
|
|
float x,
|
|
float y)
|
|
{
|
|
GskRenderNode *node;
|
|
float dx, dy;
|
|
|
|
gtk_snapshot_ensure_translate (snapshot, &dx, &dy);
|
|
|
|
node = gsk_text_node_new (font,
|
|
glyphs,
|
|
color,
|
|
&GRAPHENE_POINT_INIT (x + dx, y + dy));
|
|
if (node == NULL)
|
|
return;
|
|
|
|
gtk_snapshot_append_node_internal (snapshot, node);
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_append_linear_gradient:
|
|
* @snapshot: a #GtkSnapshot
|
|
* @bounds: the rectangle to render the linear gradient into
|
|
* @start_point: the point at which the linear gradient will begin
|
|
* @end_point: the point at which the linear gradient will finish
|
|
* @stops: (array length=n_stops): a pointer to an array of #GskColorStop defining the gradient
|
|
* @n_stops: the number of elements in @stops
|
|
*
|
|
* Appends a linear gradient node with the given stops to @snapshot.
|
|
*/
|
|
void
|
|
gtk_snapshot_append_linear_gradient (GtkSnapshot *snapshot,
|
|
const graphene_rect_t *bounds,
|
|
const graphene_point_t *start_point,
|
|
const graphene_point_t *end_point,
|
|
const GskColorStop *stops,
|
|
gsize n_stops)
|
|
{
|
|
GskRenderNode *node;
|
|
graphene_rect_t real_bounds;
|
|
graphene_point_t real_start_point;
|
|
graphene_point_t real_end_point;
|
|
float scale_x, scale_y, dx, dy;
|
|
const GdkRGBA *first_color;
|
|
gboolean need_gradient = FALSE;
|
|
int i;
|
|
|
|
g_return_if_fail (snapshot != NULL);
|
|
g_return_if_fail (start_point != NULL);
|
|
g_return_if_fail (end_point != NULL);
|
|
g_return_if_fail (stops != NULL);
|
|
g_return_if_fail (n_stops > 1);
|
|
|
|
gtk_snapshot_ensure_affine (snapshot, &scale_x, &scale_y, &dx, &dy);
|
|
gtk_graphene_rect_scale_affine (bounds, scale_x, scale_y, dx, dy, &real_bounds);
|
|
real_start_point.x = scale_x * start_point->x + dx;
|
|
real_start_point.y = scale_y * start_point->y + dy;
|
|
real_end_point.x = scale_x * end_point->x + dx;
|
|
real_end_point.y = scale_y * end_point->y + dy;
|
|
|
|
first_color = &stops[0].color;
|
|
for (i = 0; i < n_stops; i ++)
|
|
{
|
|
if (!gdk_rgba_equal (first_color, &stops[i].color))
|
|
{
|
|
need_gradient = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (need_gradient)
|
|
node = gsk_linear_gradient_node_new (&real_bounds,
|
|
&real_start_point,
|
|
&real_end_point,
|
|
stops,
|
|
n_stops);
|
|
else
|
|
node = gsk_color_node_new (first_color, &real_bounds);
|
|
|
|
gtk_snapshot_append_node_internal (snapshot, node);
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_append_repeating_linear_gradient:
|
|
* @snapshot: a #GtkSnapshot
|
|
* @bounds: the rectangle to render the linear gradient into
|
|
* @start_point: the point at which the linear gradient will begin
|
|
* @end_point: the point at which the linear gradient will finish
|
|
* @stops: (array length=n_stops): a pointer to an array of #GskColorStop defining the gradient
|
|
* @n_stops: the number of elements in @stops
|
|
*
|
|
* Appends a repeating linear gradient node with the given stops to @snapshot.
|
|
*/
|
|
void
|
|
gtk_snapshot_append_repeating_linear_gradient (GtkSnapshot *snapshot,
|
|
const graphene_rect_t *bounds,
|
|
const graphene_point_t *start_point,
|
|
const graphene_point_t *end_point,
|
|
const GskColorStop *stops,
|
|
gsize n_stops)
|
|
{
|
|
GskRenderNode *node;
|
|
graphene_rect_t real_bounds;
|
|
graphene_point_t real_start_point;
|
|
graphene_point_t real_end_point;
|
|
float scale_x, scale_y, dx, dy;
|
|
|
|
g_return_if_fail (snapshot != NULL);
|
|
g_return_if_fail (start_point != NULL);
|
|
g_return_if_fail (end_point != NULL);
|
|
g_return_if_fail (stops != NULL);
|
|
g_return_if_fail (n_stops > 1);
|
|
|
|
gtk_snapshot_ensure_affine (snapshot, &scale_x, &scale_y, &dx, &dy);
|
|
gtk_graphene_rect_scale_affine (bounds, scale_x, scale_y, dx, dy, &real_bounds);
|
|
real_start_point.x = scale_x * start_point->x + dx;
|
|
real_start_point.y = scale_y * start_point->y + dy;
|
|
real_end_point.x = scale_x * end_point->x + dx;
|
|
real_end_point.y = scale_y * end_point->y + dy;
|
|
|
|
node = gsk_repeating_linear_gradient_node_new (&real_bounds,
|
|
&real_start_point,
|
|
&real_end_point,
|
|
stops,
|
|
n_stops);
|
|
|
|
gtk_snapshot_append_node_internal (snapshot, node);
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_append_radial_gradient:
|
|
* @snapshot: a #GtkSnapshot
|
|
* @bounds: the rectangle to render the readial gradient into
|
|
* @center: the center point for the radial gradient
|
|
* @hradius: the horizontal radius
|
|
* @vradius: the vertical radius
|
|
* @start: the start position (on the horizontal axis)
|
|
* @end: the end position (on the horizontal axis)
|
|
* @stops: (array length=n_stops): a pointer to an array of #GskColorStop defining the gradient
|
|
* @n_stops: the number of elements in @stops
|
|
*
|
|
* Appends a radial gradient node with the given stops to @snapshot.
|
|
*/
|
|
void
|
|
gtk_snapshot_append_radial_gradient (GtkSnapshot *snapshot,
|
|
const graphene_rect_t *bounds,
|
|
const graphene_point_t *center,
|
|
float hradius,
|
|
float vradius,
|
|
float start,
|
|
float end,
|
|
const GskColorStop *stops,
|
|
gsize n_stops)
|
|
{
|
|
GskRenderNode *node;
|
|
graphene_rect_t real_bounds;
|
|
graphene_point_t real_center;
|
|
float scale_x, scale_y, dx, dy;
|
|
|
|
g_return_if_fail (snapshot != NULL);
|
|
g_return_if_fail (center != NULL);
|
|
g_return_if_fail (stops != NULL);
|
|
g_return_if_fail (n_stops > 1);
|
|
|
|
gtk_snapshot_ensure_affine (snapshot, &scale_x, &scale_y, &dx, &dy);
|
|
gtk_graphene_rect_scale_affine (bounds, scale_x, scale_y, dx, dy, &real_bounds);
|
|
real_center.x = scale_x * center->x + dx;
|
|
real_center.y = scale_y * center->y + dy;
|
|
|
|
node = gsk_radial_gradient_node_new (&real_bounds,
|
|
&real_center,
|
|
hradius * scale_x,
|
|
vradius * scale_y,
|
|
start,
|
|
end,
|
|
stops,
|
|
n_stops);
|
|
|
|
gtk_snapshot_append_node_internal (snapshot, node);
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_append_repeating_radial_gradient:
|
|
* @snapshot: a #GtkSnapshot
|
|
* @bounds: the rectangle to render the readial gradient into
|
|
* @center: the center point for the radial gradient
|
|
* @hradius: the horizontal radius
|
|
* @vradius: the vertical radius
|
|
* @start: the start position (on the horizontal axis)
|
|
* @end: the end position (on the horizontal axis)
|
|
* @stops: (array length=n_stops): a pointer to an array of #GskColorStop defining the gradient
|
|
* @n_stops: the number of elements in @stops
|
|
*
|
|
* Appends a repeating radial gradient node with the given stops to @snapshot.
|
|
*/
|
|
void
|
|
gtk_snapshot_append_repeating_radial_gradient (GtkSnapshot *snapshot,
|
|
const graphene_rect_t *bounds,
|
|
const graphene_point_t *center,
|
|
float hradius,
|
|
float vradius,
|
|
float start,
|
|
float end,
|
|
const GskColorStop *stops,
|
|
gsize n_stops)
|
|
{
|
|
GskRenderNode *node;
|
|
graphene_rect_t real_bounds;
|
|
graphene_point_t real_center;
|
|
float scale_x, scale_y, dx, dy;
|
|
|
|
g_return_if_fail (snapshot != NULL);
|
|
g_return_if_fail (center != NULL);
|
|
g_return_if_fail (stops != NULL);
|
|
g_return_if_fail (n_stops > 1);
|
|
|
|
gtk_snapshot_ensure_affine (snapshot, &scale_x, &scale_y, &dx, &dy);
|
|
gtk_graphene_rect_scale_affine (bounds, scale_x, scale_y, dx, dy, &real_bounds);
|
|
real_center.x = scale_x * center->x + dx;
|
|
real_center.y = scale_y * center->y + dy;
|
|
|
|
node = gsk_repeating_radial_gradient_node_new (&real_bounds,
|
|
&real_center,
|
|
hradius * scale_x,
|
|
vradius * scale_y,
|
|
start,
|
|
end,
|
|
stops,
|
|
n_stops);
|
|
|
|
gtk_snapshot_append_node_internal (snapshot, node);
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_append_border:
|
|
* @snapshot: a #GtkSnapshot
|
|
* @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.
|
|
*
|
|
* Appends a stroked border rectangle inside the given @outline. The
|
|
* 4 sides of the border can have different widths and colors.
|
|
**/
|
|
void
|
|
gtk_snapshot_append_border (GtkSnapshot *snapshot,
|
|
const GskRoundedRect *outline,
|
|
const float border_width[4],
|
|
const GdkRGBA border_color[4])
|
|
{
|
|
GskRenderNode *node;
|
|
GskRoundedRect real_outline;
|
|
float scale_x, scale_y, dx, dy;
|
|
|
|
g_return_if_fail (snapshot != NULL);
|
|
g_return_if_fail (outline != NULL);
|
|
g_return_if_fail (border_width != NULL);
|
|
g_return_if_fail (border_color != NULL);
|
|
|
|
gtk_snapshot_ensure_affine (snapshot, &scale_x, &scale_y, &dx, &dy);
|
|
gtk_rounded_rect_scale_affine (&real_outline, outline, scale_x, scale_y, dx, dy);
|
|
|
|
node = gsk_border_node_new (&real_outline, border_width, border_color);
|
|
|
|
gtk_snapshot_append_node_internal (snapshot, node);
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_append_inset_shadow:
|
|
* @snapshot: a #GtkSnapshot
|
|
* @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
|
|
*
|
|
* Appends an inset shadow into the box given by @outline.
|
|
*/
|
|
void
|
|
gtk_snapshot_append_inset_shadow (GtkSnapshot *snapshot,
|
|
const GskRoundedRect *outline,
|
|
const GdkRGBA *color,
|
|
float dx,
|
|
float dy,
|
|
float spread,
|
|
float blur_radius)
|
|
{
|
|
GskRenderNode *node;
|
|
GskRoundedRect real_outline;
|
|
float scale_x, scale_y, x, y;
|
|
|
|
g_return_if_fail (snapshot != NULL);
|
|
g_return_if_fail (outline != NULL);
|
|
g_return_if_fail (color != NULL);
|
|
|
|
gtk_snapshot_ensure_affine (snapshot, &scale_x, &scale_y, &x, &y);
|
|
gtk_rounded_rect_scale_affine (&real_outline, outline, scale_x, scale_y, x, y);
|
|
|
|
node = gsk_inset_shadow_node_new (&real_outline,
|
|
color,
|
|
scale_x * dx,
|
|
scale_y * dy,
|
|
spread,
|
|
blur_radius);
|
|
|
|
gtk_snapshot_append_node_internal (snapshot, node);
|
|
}
|
|
|
|
/**
|
|
* gtk_snapshot_append_outset_shadow:
|
|
* @snapshot: a #GtkSnapshot
|
|
* @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 outside
|
|
* @blur_radius: how much blur to apply to the shadow
|
|
*
|
|
* Appends an outset shadow node around the box given by @outline.
|
|
*/
|
|
void
|
|
gtk_snapshot_append_outset_shadow (GtkSnapshot *snapshot,
|
|
const GskRoundedRect *outline,
|
|
const GdkRGBA *color,
|
|
float dx,
|
|
float dy,
|
|
float spread,
|
|
float blur_radius)
|
|
{
|
|
GskRenderNode *node;
|
|
GskRoundedRect real_outline;
|
|
float scale_x, scale_y, x, y;
|
|
|
|
g_return_if_fail (snapshot != NULL);
|
|
g_return_if_fail (outline != NULL);
|
|
g_return_if_fail (color != NULL);
|
|
|
|
gtk_snapshot_ensure_affine (snapshot, &scale_x, &scale_y, &x, &y);
|
|
gtk_rounded_rect_scale_affine (&real_outline, outline, scale_x, scale_y, x, y);
|
|
|
|
node = gsk_outset_shadow_node_new (&real_outline,
|
|
color,
|
|
scale_x * dx,
|
|
scale_y * dy,
|
|
spread,
|
|
blur_radius);
|
|
|
|
|
|
gtk_snapshot_append_node_internal (snapshot, node);
|
|
}
|
|
|