/* GTK - The GIMP Toolkit * Copyright (C) 2016 Benjamin Otte * * 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 . */ #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 "gdk/gdkrgbaprivate.h" #include "gsk/gskrendernodeprivate.h" #include "gsk/gskroundedrectprivate.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" /** * GtkSnapshot: * * `GtkSnapshot` assists in creating [class@Gsk.RenderNode]s for widgets. * * 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 one that `gtk_snapshot_append_…()` * functions operate on. Use the `gtk_snapshot_push_…()` functions and * [method@Snapshot.pop] to change the current node. * * The typical way to obtain a `GtkSnapshot` object is as an argument to * the [vfunc@Gtk.Widget.snapshot] vfunc. If you need to create your own * `GtkSnapshot`, use [ctor@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)) { GskRenderNode *node = gtk_snapshot_to_node (snapshot); g_clear_pointer (&node, gsk_render_node_unref); } 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 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); } static void gtk_snapshot_init (GtkSnapshot *self) { gtk_snapshot_states_init (&self->state_stack); gtk_snapshot_nodes_init (&self->nodes); gtk_snapshot_push_state (self, NULL, gtk_snapshot_collect_default, NULL); } /** * gtk_snapshot_new: * * Creates a new `GtkSnapshot`. * * Returns: a newly-allocated `GtkSnapshot` */ GtkSnapshot * gtk_snapshot_new (void) { return g_object_new (GTK_TYPE_SNAPSHOT, NULL); } /** * 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) (nullable): a newly-created [class@Gsk.RenderNode] */ 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: (nullable): 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) (nullable): a newly-created [iface@Gdk.Paintable] */ 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) { GdkRGBA color = GDK_RGBA ("00000000"); graphene_rect_t bounds; gsk_render_node_get_bounds (node, &bounds); opacity_node = gsk_color_node_new (&color, &bounds); gsk_render_node_unref (node); } 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 [method@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; if (radius < 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. Must be positive * * Blurs an image. * * The image is recorded until the next call to [method@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 [method@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_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 [method@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 [method@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 [class@Gsk.GLShaderNode]. * * The node uses the given [class@Gsk.GLShader] and uniform values * Additionally this takes a list of @n_children other nodes * which will be passed to the [class@Gsk.GLShaderNode]. * * 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 [method@Gsk.GLShader.format_args] * or [struct@Gsk.ShaderArgsBuilder]. * * 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 [method@Gsk.GLShader.compile] to ensure the @shader * will work for the renderer before using it. * * If the shader requires textures (see [method@Gsk.GLShader.get_n_textures]), * then it is expected that you call [method@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 `GskGLShaderNode`, 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 * [method@Gtk.Snapshot.pop]. * * If you want to use pre-existing textures as input to the shader rather * than rendering new ones, use [method@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 [class@Gsk.GLShader]. */ 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 [method@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); gsk_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: (array length=n_shadows): 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 [method@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 two images with the given blend mode. * * Until the first call to [method@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 [method@Gtk.Snapshot.pop]. * * Calling this function requires two subsequent calls * to [method@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 [method@Gtk.Snapshot.pop], the start image * will be snapshot. After that call, the end image will be recorded * until the second call to [method@Gtk.Snapshot.pop]. * * Calling this function requires two subsequent calls * to [method@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, FALSE, 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, FALSE, 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, gboolean is_texture_pop) { 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); } if (is_texture_pop && (state->collect_func != gtk_snapshot_collect_gl_shader_texture)) { g_critical ("Unexpected call to gtk_snapshot_gl_shader_pop_texture()."); return NULL; } else if (!is_texture_pop && (state->collect_func == gtk_snapshot_collect_gl_shader_texture)) { g_critical ("Expected a call to gtk_snapshot_gl_shader_pop_texture()."); return NULL; } return gtk_snapshot_pop_one (snapshot); } /** * gtk_snapshot_push_collect: * * Private. * * Pushes 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, FALSE); 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 [method@GObject.Object.unref]. * * Returns: (transfer full) (nullable): the constructed `GskRenderNode` */ GskRenderNode * gtk_snapshot_to_node (GtkSnapshot *snapshot) { GskRenderNode *result; result = gtk_snapshot_pop_internal (snapshot, FALSE); /* 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: (nullable): 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 [method@GObject.Object.unref]. * * Returns: (transfer full) (nullable): 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) { GskRenderNode *node; node = gtk_snapshot_pop_internal (snapshot, FALSE); 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 [class@Gsk.GLShaderNode] below it. * * This must be called the same number of times as the number * of textures is needed for the shader in * [method@Gtk.Snapshot.push_gl_shader]. */ void gtk_snapshot_gl_shader_pop_texture (GtkSnapshot *snapshot) { G_GNUC_UNUSED GskRenderNode *node; node = gtk_snapshot_pop_internal (snapshot, TRUE); 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. * * When [method@Gtk.Snapshot.restore] is called, @snapshot will * be restored to the saved state. Multiple calls to * [method@Snapshot.save] and [class@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 * [method@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: (nullable): 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. * * To rotate around other axes, use [method@Gsk.Transform.rotate_3d]. */ 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 [method@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 [method@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 [method@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 [class@Gsk.CairoNode] 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 texture 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 color 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 style context that defines the background * @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 style context that defines the frame * @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 style context that defines the focus ring * @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 style context that defines the text * @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): the color stops 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; float scale_x, scale_y, dx, dy; const GdkRGBA *first_color; gboolean need_gradient = FALSE; 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); first_color = &stops[0].color; for (gsize i = 0; i < n_stops; i ++) { if (!gdk_rgba_equal (first_color, &stops[i].color)) { need_gradient = TRUE; break; } } if (need_gradient) { graphene_point_t real_start_point, real_end_point; 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_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): the color stops 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; float scale_x, scale_y, dx, dy; gboolean need_gradient = FALSE; const GdkRGBA *first_color; 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); first_color = &stops[0].color; for (gsize i = 0; i < n_stops; i ++) { if (!gdk_rgba_equal (first_color, &stops[i].color)) { need_gradient = TRUE; break; } } if (need_gradient) { graphene_point_t real_start_point, real_end_point; 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); } else { node = gsk_color_node_new (first_color, &real_bounds); } gtk_snapshot_append_node_internal (snapshot, node); } /** * gtk_snapshot_append_conic_gradient: * @snapshot: a `GtkSnapshot` * @bounds: the rectangle to render the gradient into * @center: the center point of the conic gradient * @rotation: the clockwise rotation in degrees of the starting angle. * 0 means the starting angle is the top. * @stops: (array length=n_stops): the color stops defining the gradient * @n_stops: the number of elements in @stops * * Appends a conic gradient node with the given stops to @snapshot. */ void gtk_snapshot_append_conic_gradient (GtkSnapshot *snapshot, const graphene_rect_t *bounds, const graphene_point_t *center, float rotation, const GskColorStop *stops, gsize n_stops) { GskRenderNode *node; graphene_rect_t real_bounds; float dx, dy; const GdkRGBA *first_color; gboolean need_gradient = FALSE; int i; 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_translate (snapshot, &dx, &dy); graphene_rect_offset_r (bounds, dx, dy, &real_bounds); 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_conic_gradient_node_new (&real_bounds, &GRAPHENE_POINT_INIT( center->x + dx, center->y + dy ), rotation, stops, n_stops); else node = gsk_color_node_new (first_color, &real_bounds); 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): the color stops 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; float scale_x, scale_y, dx, dy; gboolean need_gradient = FALSE; const GdkRGBA *first_color; 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); first_color = &stops[0].color; for (gsize i = 0; i < n_stops; i ++) { if (!gdk_rgba_equal (first_color, &stops[i].color)) { need_gradient = TRUE; break; } } if (need_gradient) { graphene_point_t real_center; 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); } else { node = gsk_color_node_new (first_color, &real_bounds); } 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): the color stops 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; float scale_x, scale_y, dx, dy; gboolean need_gradient = FALSE; const GdkRGBA *first_color; 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); first_color = &stops[0].color; for (gsize i = 0; i < n_stops; i ++) { if (!gdk_rgba_equal (first_color, &stops[i].color)) { need_gradient = TRUE; break; } } if (need_gradient) { graphene_point_t real_center; 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); } else { node = gsk_color_node_new (first_color, &real_bounds); } gtk_snapshot_append_node_internal (snapshot, node); } /** * gtk_snapshot_append_border: * @snapshot: a `GtkSnapshot` * @outline: 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 four 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); gsk_rounded_rect_scale_affine (&real_outline, outline, scale_x, scale_y, dx, dy); node = gsk_border_node_new (&real_outline, (float[4]) { border_width[0] * scale_y, border_width[1] * scale_x, border_width[2] * scale_y, border_width[3] * scale_x, }, 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); gsk_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); gsk_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); }