/* 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 "gtkcssrgbavalueprivate.h" #include "gtkcssshadowsvalueprivate.h" #include "gtkrenderbackgroundprivate.h" #include "gtkrenderborderprivate.h" #include "gtkrendericonprivate.h" #include "gtkstylecontextprivate.h" #include "gsk/gskrendernodeprivate.h" #include "gtk/gskpango.h" /** * SECTION:gtksnapshot * @Short_description: Auxiliary object for snapshots * @Title: GtkSnapshot * * GtkSnapshot is an auxiliary object that assists in creating #GskRenderNodes * in the #GtkWidget::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() * operates on. Use the gtk_snapshot_push() and gtk_snapshot_pop() functions to * change the current node. * * The typical way to obtain a #GtkSnapshot object is as an argument to * the #GtkWidget::snapshot vfunc. If you need to create your own GtkSnapshot, * use gtk_snapshot_new(). */ G_DEFINE_BOXED_TYPE (GtkSnapshot, gtk_snapshot, gtk_snapshot_ref, gtk_snapshot_unref) /** * gtk_snapshot_ref: * @snapshot: a #GtkSnapshot * * Increase the reference count of @snapshot by 1. * * Returns: the @snapshot */ GtkSnapshot * gtk_snapshot_ref (GtkSnapshot *snapshot) { g_assert (snapshot->ref_count > 0); snapshot->ref_count += 1; return snapshot; } /** * gtk_snapshot_unref: * @snapshot: a #GtkSnapshot * * Decrease the reference count of @snapshot by 1 and * free the object if the count drops to 0. */ void gtk_snapshot_unref (GtkSnapshot *snapshot) { g_assert (snapshot->ref_count > 0); snapshot->ref_count -= 1; if (snapshot->ref_count > 0) return; if (snapshot->state_stack) gsk_render_node_unref (gtk_snapshot_to_node (snapshot)); g_assert (snapshot->state_stack == NULL); g_assert (snapshot->nodes == NULL); g_free (snapshot); } static GskRenderNode * gtk_snapshot_collect_default (GtkSnapshot *snapshot, GtkSnapshotState *state, GskRenderNode **nodes, guint n_nodes, const char *name) { 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); if (name) gsk_render_node_set_name (node, name); } return node; } static GtkSnapshotState * gtk_snapshot_push_state (GtkSnapshot *snapshot, char *name, cairo_region_t *clip, int translate_x, int translate_y, GtkSnapshotCollectFunc collect_func) { GtkSnapshotState *state; g_array_set_size (snapshot->state_stack, snapshot->state_stack->len + 1); state = &g_array_index (snapshot->state_stack, GtkSnapshotState, snapshot->state_stack->len - 1); state->name = name; if (clip) state->clip_region = cairo_region_reference (clip); state->translate_x = translate_x; state->translate_y = translate_y; state->collect_func = collect_func; state->start_node_index = snapshot->nodes->len; state->n_nodes = 0; return state; } static GtkSnapshotState * gtk_snapshot_get_current_state (const GtkSnapshot *snapshot) { g_assert (snapshot->state_stack->len > 0); return &g_array_index (snapshot->state_stack, GtkSnapshotState, snapshot->state_stack->len - 1); } static GtkSnapshotState * gtk_snapshot_get_previous_state (const GtkSnapshot *snapshot) { g_assert (snapshot->state_stack->len > 1); return &g_array_index (snapshot->state_stack, GtkSnapshotState, snapshot->state_stack->len - 2); } static void gtk_snapshot_state_clear (GtkSnapshotState *state) { g_clear_pointer (&state->clip_region, cairo_region_destroy); g_clear_pointer (&state->name, g_free); } /** * gtk_snapshot_new: * @renderer: the #GskRenderer to create nodes for * @record_names: whether to keep node names (for debugging purposes) * @clip: (nullable): the clip region to use, or %NULL * @name: a printf-style format string to create the node name * @...: arguments for @name * * Creates a new #GtkSnapshot. * * Returns: a newly-allocated #GtkSnapshot */ GtkSnapshot * gtk_snapshot_new (GskRenderer *renderer, gboolean record_names, const cairo_region_t *clip, const char *name, ...) { GtkSnapshot *snapshot; char *str; if (name && record_names) { va_list args; va_start (args, name); str = g_strdup_vprintf (name, args); va_end (args); } else str = NULL; snapshot = g_new (GtkSnapshot, 1); snapshot->ref_count = 1; snapshot->record_names = record_names; snapshot->renderer = renderer; snapshot->state_stack = g_array_new (FALSE, TRUE, sizeof (GtkSnapshotState)); g_array_set_clear_func (snapshot->state_stack, (GDestroyNotify)gtk_snapshot_state_clear); snapshot->nodes = g_ptr_array_new_with_free_func ((GDestroyNotify)gsk_render_node_unref); gtk_snapshot_push_state (snapshot, str, (cairo_region_t *) clip, 0, 0, gtk_snapshot_collect_default); return snapshot; } /** * gtk_snapshot_free_to_node: * @snapshot: a #GtkSnapshot * * Returns the node that was constructed by @snapshot * and frees @snapshot. * * Returns: a newly-created #GskRenderNode */ GskRenderNode * gtk_snapshot_free_to_node (GtkSnapshot *snapshot) { GskRenderNode *result; result = gtk_snapshot_to_node (snapshot); gtk_snapshot_unref (snapshot); return result; } /** * gtk_snapshot_push: * @snapshot: a #GtkSnapshot * @keep_coordinates: If %TRUE, the current offset and clip will be kept. * Otherwise, the clip will be unset and the offset will be reset to * (0, 0). * @name: (transfer none): a printf() style format string for the name for the new node * @...: arguments to insert into the format string * * Creates a new render node, appends it to the current render * node of @snapshot, and makes it the new current render node. */ void gtk_snapshot_push (GtkSnapshot *snapshot, gboolean keep_coordinates, const char *name, ...) { char *str; if (name && snapshot->record_names) { va_list args; va_start (args, name); str = g_strdup_vprintf (name, args); va_end (args); } else str = NULL; if (keep_coordinates) { GtkSnapshotState *state = gtk_snapshot_get_current_state (snapshot); gtk_snapshot_push_state (snapshot, g_strdup (str), state->clip_region, state->translate_x, state->translate_y, gtk_snapshot_collect_default); } else { gtk_snapshot_push_state (snapshot, g_strdup (str), NULL, 0, 0, gtk_snapshot_collect_default); } } static GskRenderNode * gtk_snapshot_collect_transform (GtkSnapshot *snapshot, GtkSnapshotState *state, GskRenderNode **nodes, guint n_nodes, const char *name) { GskRenderNode *node, *transform_node; node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes, name); if (node == NULL) return NULL; transform_node = gsk_transform_node_new (node, &state->data.transform.transform); if (name) gsk_render_node_set_name (transform_node, name); gsk_render_node_unref (node); return transform_node; } void gtk_snapshot_push_transform (GtkSnapshot *snapshot, const graphene_matrix_t *transform, const char *name, ...) { GtkSnapshotState *previous_state; GtkSnapshotState *state; graphene_matrix_t offset; char *str; if (name && snapshot->record_names) { va_list args; va_start (args, name); str = g_strdup_vprintf (name, args); va_end (args); } else str = NULL; state = gtk_snapshot_push_state (snapshot, str, NULL, 0, 0, gtk_snapshot_collect_transform); previous_state = gtk_snapshot_get_previous_state (snapshot); graphene_matrix_init_translate (&offset, &GRAPHENE_POINT3D_INIT( previous_state->translate_x, previous_state->translate_y, 0 )); graphene_matrix_multiply (transform, &offset, &state->data.transform.transform); } static GskRenderNode * gtk_snapshot_collect_opacity (GtkSnapshot *snapshot, GtkSnapshotState *state, GskRenderNode **nodes, guint n_nodes, const char *name) { GskRenderNode *node, *opacity_node; node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes, name); 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); if (name) gsk_render_node_set_name (opacity_node, name); gsk_render_node_unref (node); } return opacity_node; } void gtk_snapshot_push_opacity (GtkSnapshot *snapshot, double opacity, const char *name, ...) { GtkSnapshotState *current_state = gtk_snapshot_get_current_state (snapshot); GtkSnapshotState *state; char *str; if (name && snapshot->record_names) { va_list args; va_start (args, name); str = g_strdup_vprintf (name, args); va_end (args); } else str = NULL; state = gtk_snapshot_push_state (snapshot, str, current_state->clip_region, current_state->translate_x, current_state->translate_y, gtk_snapshot_collect_opacity); 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, const char *name) { GskRenderNode *node, *blur_node; double radius; node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes, name); if (node == NULL) return NULL; radius = state->data.blur.radius; if (radius == 0.0) return node; blur_node = gsk_blur_node_new (node, radius); if (name) gsk_render_node_set_name (blur_node, name); gsk_render_node_unref (node); return blur_node; } void gtk_snapshot_push_blur (GtkSnapshot *snapshot, double radius, const char *name, ...) { const GtkSnapshotState *current_state = gtk_snapshot_get_current_state (snapshot); GtkSnapshotState *state; char *str; if (name && snapshot->record_names) { va_list args; va_start (args, name); str = g_strdup_vprintf (name, args); va_end (args); } else str = NULL; state = gtk_snapshot_push_state (snapshot, str, current_state->clip_region, current_state->translate_x, current_state->translate_y, gtk_snapshot_collect_blur); state->data.blur.radius = radius; current_state = state; } static GskRenderNode * gtk_snapshot_collect_color_matrix (GtkSnapshot *snapshot, GtkSnapshotState *state, GskRenderNode **nodes, guint n_nodes, const char *name) { GskRenderNode *node, *color_matrix_node; node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes, name); if (node == NULL) return NULL; if (gsk_render_node_get_node_type (node) == GSK_COLOR_MATRIX_NODE) { GskRenderNode *child = gsk_render_node_ref (gsk_color_matrix_node_get_child (node)); const graphene_matrix_t *mat1 = gsk_color_matrix_node_peek_color_matrix (node); graphene_matrix_t mat2; graphene_vec4_t offset2; /* 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. */ mat2 = state->data.color_matrix.matrix; offset2 = state->data.color_matrix.offset; graphene_matrix_transform_vec4 (mat1, &offset2, &offset2); graphene_vec4_add (&offset2, gsk_color_matrix_node_peek_color_offset (node), &offset2); graphene_matrix_multiply (mat1, &mat2, &mat2); gsk_render_node_unref (node); node = NULL; color_matrix_node = gsk_color_matrix_node_new (child, &mat2, &offset2); gsk_render_node_unref (child); } else { color_matrix_node = gsk_color_matrix_node_new (node, &state->data.color_matrix.matrix, &state->data.color_matrix.offset); gsk_render_node_unref (node); } if (name) gsk_render_node_set_name (color_matrix_node, name); return color_matrix_node; } void gtk_snapshot_push_color_matrix (GtkSnapshot *snapshot, const graphene_matrix_t *color_matrix, const graphene_vec4_t *color_offset, const char *name, ...) { const GtkSnapshotState *current_state = gtk_snapshot_get_current_state (snapshot); GtkSnapshotState *state; char *str; if (name && snapshot->record_names) { va_list args; va_start (args, name); str = g_strdup_vprintf (name, args); va_end (args); } else str = NULL; state = gtk_snapshot_push_state (snapshot, str, current_state->clip_region, current_state->translate_x, current_state->translate_y, gtk_snapshot_collect_color_matrix); graphene_matrix_init_from_matrix (&state->data.color_matrix.matrix, color_matrix); graphene_vec4_init_from_vec4 (&state->data.color_matrix.offset, color_offset); current_state = state; } static void rectangle_init_from_graphene (cairo_rectangle_int_t *cairo, const graphene_rect_t *graphene) { cairo->x = floorf (graphene->origin.x); cairo->y = floorf (graphene->origin.y); cairo->width = ceilf (graphene->origin.x + graphene->size.width) - cairo->x; cairo->height = ceilf (graphene->origin.y + graphene->size.height) - cairo->y; } static GskRenderNode * gtk_snapshot_collect_repeat (GtkSnapshot *snapshot, GtkSnapshotState *state, GskRenderNode **nodes, guint n_nodes, const char *name) { GskRenderNode *node, *repeat_node; graphene_rect_t *bounds = &state->data.repeat.bounds; graphene_rect_t *child_bounds = &state->data.repeat.child_bounds; node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes, name); if (node == NULL) return NULL; repeat_node = gsk_repeat_node_new (bounds, node, child_bounds->size.width > 0 ? child_bounds : NULL); if (name) gsk_render_node_set_name (repeat_node, name); gsk_render_node_unref (node); return repeat_node; } void gtk_snapshot_push_repeat (GtkSnapshot *snapshot, const graphene_rect_t *bounds, const graphene_rect_t *child_bounds, const char *name, ...) { const GtkSnapshotState *current_state = gtk_snapshot_get_current_state (snapshot); GtkSnapshotState *state; cairo_region_t *clip = NULL; graphene_rect_t real_child_bounds = { { 0 } }; char *str; if (name && snapshot->record_names) { va_list args; va_start (args, name); str = g_strdup_vprintf (name, args); va_end (args); } else str = NULL; if (child_bounds) { cairo_rectangle_int_t rect; graphene_rect_offset_r (child_bounds, current_state->translate_x, current_state->translate_y, &real_child_bounds); rectangle_init_from_graphene (&rect, &real_child_bounds); clip = cairo_region_create_rectangle (&rect); } state = gtk_snapshot_push_state (snapshot, str, clip, current_state->translate_x, current_state->translate_y, gtk_snapshot_collect_repeat); current_state = gtk_snapshot_get_previous_state (snapshot); graphene_rect_offset_r (bounds, current_state->translate_x, current_state->translate_y, &state->data.repeat.bounds); state->data.repeat.child_bounds = real_child_bounds; current_state = state; if (clip) cairo_region_destroy (clip); } static GskRenderNode * gtk_snapshot_collect_clip (GtkSnapshot *snapshot, GtkSnapshotState *state, GskRenderNode **nodes, guint n_nodes, const char *name) { GskRenderNode *node, *clip_node; node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes, name); 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); if (name) gsk_render_node_set_name (clip_node, name); gsk_render_node_unref (node); return clip_node; } void gtk_snapshot_push_clip (GtkSnapshot *snapshot, const graphene_rect_t *bounds, const char *name, ...) { const GtkSnapshotState *current_state = gtk_snapshot_get_current_state (snapshot); GtkSnapshotState *state; graphene_rect_t real_bounds; cairo_region_t *clip; cairo_rectangle_int_t rect; char *str; graphene_rect_offset_r (bounds, current_state->translate_x, current_state->translate_y, &real_bounds); if (name && snapshot->record_names) { va_list args; va_start (args, name); str = g_strdup_vprintf (name, args); va_end (args); } else str = NULL; rectangle_init_from_graphene (&rect, &real_bounds); if (current_state->clip_region) { clip = cairo_region_copy (current_state->clip_region); cairo_region_intersect_rectangle (clip, &rect); } else { clip = cairo_region_create_rectangle (&rect); } state = gtk_snapshot_push_state (snapshot, str, clip, current_state->translate_x, current_state->translate_y, gtk_snapshot_collect_clip); state->data.clip.bounds = real_bounds; cairo_region_destroy (clip); } static GskRenderNode * gtk_snapshot_collect_rounded_clip (GtkSnapshot *snapshot, GtkSnapshotState *state, GskRenderNode **nodes, guint n_nodes, const char *name) { GskRenderNode *node, *clip_node; node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes, name); 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) return NULL; if (name) gsk_render_node_set_name (clip_node, name); gsk_render_node_unref (node); return clip_node; } void gtk_snapshot_push_rounded_clip (GtkSnapshot *snapshot, const GskRoundedRect *bounds, const char *name, ...) { const GtkSnapshotState *current_state = gtk_snapshot_get_current_state (snapshot); GtkSnapshotState *state; GskRoundedRect real_bounds; cairo_region_t *clip; cairo_rectangle_int_t rect; char *str; gsk_rounded_rect_init_copy (&real_bounds, bounds); gsk_rounded_rect_offset (&real_bounds, current_state->translate_x, current_state->translate_y); if (name && snapshot->record_names) { va_list args; va_start (args, name); str = g_strdup_vprintf (name, args); va_end (args); } else str = NULL; rectangle_init_from_graphene (&rect, &real_bounds.bounds); if (current_state->clip_region) { clip = cairo_region_copy (current_state->clip_region); cairo_region_intersect_rectangle (clip, &rect); } else { clip = cairo_region_create_rectangle (&rect); } state = gtk_snapshot_push_state (snapshot, str, clip, current_state->translate_x, current_state->translate_y, gtk_snapshot_collect_rounded_clip); state->data.rounded_clip.bounds = real_bounds; current_state = state; cairo_region_destroy (clip); } static GskRenderNode * gtk_snapshot_collect_shadow (GtkSnapshot *snapshot, GtkSnapshotState *state, GskRenderNode **nodes, guint n_nodes, const char *name) { GskRenderNode *node, *shadow_node; node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes, name); 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); if (name) gsk_render_node_set_name (shadow_node, name); gsk_render_node_unref (node); g_free (state->data.shadow.shadows); return shadow_node; } void gtk_snapshot_push_shadow (GtkSnapshot *snapshot, const GskShadow *shadow, gsize n_shadows, const char *name, ...) { const GtkSnapshotState *current_state = gtk_snapshot_get_current_state (snapshot); GtkSnapshotState *state; char *str; if (name && snapshot->record_names) { va_list args; va_start (args, name); str = g_strdup_vprintf (name, args); va_end (args); } else str = NULL; state = gtk_snapshot_push_state (snapshot, str, current_state->clip_region, current_state->translate_x, current_state->translate_y, gtk_snapshot_collect_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, const char *name) { GskRenderNode *bottom_node, *top_node, *blend_node; GdkRGBA transparent = { 0, 0, 0, 0 }; top_node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes, name); bottom_node = state->data.blend.bottom_node; 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_set_name (blend_node, name); gsk_render_node_unref (top_node); gsk_render_node_unref (bottom_node); return blend_node; } static GskRenderNode * gtk_snapshot_collect_blend_bottom (GtkSnapshot *snapshot, GtkSnapshotState *state, GskRenderNode **nodes, guint n_nodes, const char *name) { 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, name); return NULL; } /** * gtk_snapshot_push_blend: * @snapshot: a #GtkSnapshot * @blend_mode: blend mode to use * @name: printf format string for name of the pushed node * @...: printf-style arguments for the @name string * * 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, const char *name, ...) { GtkSnapshotState *current_state = gtk_snapshot_get_current_state (snapshot); GtkSnapshotState *top_state; char *str; if (name && snapshot->record_names) { va_list args; va_start (args, name); str = g_strdup_vprintf (name, args); va_end (args); } else str = NULL; top_state = gtk_snapshot_push_state (snapshot, str, current_state->clip_region, current_state->translate_x, current_state->translate_y, gtk_snapshot_collect_blend_top); top_state->data.blend.blend_mode = blend_mode; gtk_snapshot_push_state (snapshot, g_strdup (str), top_state->clip_region, top_state->translate_x, top_state->translate_y, gtk_snapshot_collect_blend_bottom); } static GskRenderNode * gtk_snapshot_collect_cross_fade_end (GtkSnapshot *snapshot, GtkSnapshotState *state, GskRenderNode **nodes, guint n_nodes, const char *name) { GskRenderNode *start_node, *end_node, *node; end_node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes, name); start_node = state->data.cross_fade.start_node; 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_set_name (node, name); 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_set_name (node, name); 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_set_name (node, name); gsk_render_node_unref (end_node); } else { node = NULL; } return node; } static GskRenderNode * gtk_snapshot_collect_cross_fade_start (GtkSnapshot *snapshot, GtkSnapshotState *state, GskRenderNode **nodes, guint n_nodes, const char *name) { 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, name); return NULL; } /** * gtk_snapshot_push_cross_fade: * @snapshot: a #GtkSnapshot * @progress: progress between 0.0 and 1.0 * @name: printf format string for name of the pushed node * @...: printf-style arguments for the @name string * * 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 char *name, ...) { const GtkSnapshotState *current_state = gtk_snapshot_get_current_state (snapshot); GtkSnapshotState *end_state; char *str; if (name && snapshot->record_names) { va_list args; va_start (args, name); str = g_strdup_vprintf (name, args); va_end (args); } else str = NULL; end_state = gtk_snapshot_push_state (snapshot, str, current_state->clip_region, current_state->translate_x, current_state->translate_y, gtk_snapshot_collect_cross_fade_end); end_state->data.cross_fade.progress = progress; gtk_snapshot_push_state (snapshot, g_strdup (str), end_state->clip_region, end_state->translate_x, end_state->translate_y, gtk_snapshot_collect_cross_fade_start); } static GskRenderNode * gtk_snapshot_pop_internal (GtkSnapshot *snapshot) { GtkSnapshotState *state; guint state_index; GskRenderNode *node; if (snapshot->state_stack->len == 0) { g_warning ("Too many gtk_snapshot_pop() calls."); return NULL; } state = gtk_snapshot_get_current_state (snapshot); state_index = snapshot->state_stack->len - 1; node = state->collect_func (snapshot, state, (GskRenderNode **) snapshot->nodes->pdata + state->start_node_index, state->n_nodes, state->name); /* The collect func may not modify the state stack... */ g_assert (state_index == snapshot->state_stack->len - 1); /* Remove all the state's nodes from the list of nodes */ g_assert (state->start_node_index + state->n_nodes == snapshot->nodes->len); g_ptr_array_remove_range (snapshot->nodes, snapshot->nodes->len - state->n_nodes, state->n_nodes); g_array_remove_index (snapshot->state_stack, state_index); return node; } /** * 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 gtk_snapshot_unref(). * * Returns: the constructed #GskRenderNode */ GskRenderNode * gtk_snapshot_to_node (GtkSnapshot *snapshot) { GskRenderNode *result; /* We should have exactly our initial state */ if (snapshot->state_stack->len > 1) { gint i; g_warning ("Too many gtk_snapshot_push() calls. Still there:"); for (i = snapshot->state_stack->len - 1; i >= 0; i --) { const GtkSnapshotState *s = &g_array_index (snapshot->state_stack, GtkSnapshotState, i); g_warning ("%s", s->name); } } result = gtk_snapshot_pop_internal (snapshot); g_array_free (snapshot->state_stack, TRUE); snapshot->state_stack = NULL; g_ptr_array_free (snapshot->nodes, TRUE); snapshot->nodes = NULL; return result; } /** * 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); if (node) { gtk_snapshot_append_node (snapshot, node); gsk_render_node_unref (node); } } /** * gtk_snapshot_get_renderer: * @snapshot: a #GtkSnapshot * * Obtains the #GskRenderer that this snapshot will be * rendered with. * * Returns: (transfer none): the #GskRenderer */ GskRenderer * gtk_snapshot_get_renderer (GtkSnapshot *snapshot) { return snapshot->renderer; } /** * gtk_snapshot_get_record_names: * @snapshot: a #GtkSnapshot * * Obtains whether the snapshot is recording names * for debugging. * * Returns: whether the snapshot records names */ gboolean gtk_snapshot_get_record_names (GtkSnapshot *snapshot) { return snapshot->record_names; } /** * gtk_snapshot_offset: * @snapshot: a $GtkSnapshot * @x: horizontal translation * @y: vertical translation * * Appends a translation by (@x, @y) to the current transformation. */ void gtk_snapshot_offset (GtkSnapshot *snapshot, int x, int y) { GtkSnapshotState *current_state = gtk_snapshot_get_current_state (snapshot); current_state->translate_x += x; current_state->translate_y += y; } /** * gtk_snapshot_get_offset: * @snapshot: a #GtkSnapshot * @x: (out) (optional): return location for x offset * @y: (out) (optional): return location for y offset * * Queries the offset managed by @snapshot. This offset is the * accumulated sum of calls to gtk_snapshot_offset(). * * Use this offset to determine how to offset nodes that you * manually add to the snapshot using * gtk_snapshot_append(). * * Note that other functions that add nodes for you, such as * gtk_snapshot_append_cairo() will add this offset for * you. **/ void gtk_snapshot_get_offset (GtkSnapshot *snapshot, int *x, int *y) { const GtkSnapshotState *current_state = gtk_snapshot_get_current_state (snapshot); if (x) *x = current_state->translate_x; if (y) *y = current_state->translate_y; } /** * 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) { GtkSnapshotState *current_state; g_return_if_fail (snapshot != NULL); g_return_if_fail (GSK_IS_RENDER_NODE (node)); current_state = gtk_snapshot_get_current_state (snapshot); if (current_state) { g_ptr_array_add (snapshot->nodes, gsk_render_node_ref (node)); current_state->n_nodes ++; } else { g_critical ("Tried appending a node to an already finished snapshot."); } } /** * gtk_snapshot_append_cairo: * @snapshot: a #GtkSnapshot * @bounds: the bounds for the new node * @name: (transfer none): a printf() style format string for the name for the new node * @...: arguments to insert into the format string * * 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, const char *name, ...) { const GtkSnapshotState *current_state = gtk_snapshot_get_current_state (snapshot); GskRenderNode *node; graphene_rect_t real_bounds; cairo_t *cr; g_return_val_if_fail (snapshot != NULL, NULL); g_return_val_if_fail (bounds != NULL, NULL); graphene_rect_offset_r (bounds, current_state->translate_x, current_state->translate_y, &real_bounds); node = gsk_cairo_node_new (&real_bounds); if (name && snapshot->record_names) { va_list args; char *str; va_start (args, name); str = g_strdup_vprintf (name, args); va_end (args); gsk_render_node_set_name (node, str); g_free (str); } gtk_snapshot_append_node (snapshot, node); gsk_render_node_unref (node); cr = gsk_cairo_node_get_draw_context (node, snapshot->renderer); cairo_translate (cr, current_state->translate_x, current_state->translate_y); return cr; } /** * gtk_snapshot_append_texture: * @snapshot: a #GtkSnapshot * @texture: the #GdkTexture to render * @bounds: the bounds for the new node * @name: (transfer none): a printf() style format string for the name for the new node * @...: arguments to insert into the format string * * 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, const char *name, ...) { GtkSnapshotState *current_state = gtk_snapshot_get_current_state (snapshot); GskRenderNode *node; graphene_rect_t real_bounds; g_return_if_fail (snapshot != NULL); g_return_if_fail (GDK_IS_TEXTURE (texture)); g_return_if_fail (bounds != NULL); graphene_rect_offset_r (bounds, current_state->translate_x, current_state->translate_y, &real_bounds); node = gsk_texture_node_new (texture, &real_bounds); if (name && snapshot->record_names) { va_list args; char *str; va_start (args, name); str = g_strdup_vprintf (name, args); va_end (args); gsk_render_node_set_name (node, str); g_free (str); } gtk_snapshot_append_node (snapshot, node); gsk_render_node_unref (node); } /** * gtk_snapshot_append_color: * @snapshot: a #GtkSnapshot * @color: the #GdkRGBA to draw * @bounds: the bounds for the new node * @name: (transfer none): a printf() style format string for the name for the new node * @...: arguments to insert into the format string * * 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, const char *name, ...) { const GtkSnapshotState *current_state = gtk_snapshot_get_current_state (snapshot); GskRenderNode *node; graphene_rect_t real_bounds; g_return_if_fail (snapshot != NULL); g_return_if_fail (color != NULL); g_return_if_fail (bounds != NULL); graphene_rect_offset_r (bounds, current_state->translate_x, current_state->translate_y, &real_bounds); /* Color nodes are trivially "clippable" so we do it now */ if (current_state->clip_region) { cairo_rectangle_int_t clip_extents; cairo_region_get_extents (current_state->clip_region, &clip_extents); graphene_rect_intersection (&GRAPHENE_RECT_INIT ( clip_extents.x, clip_extents.y, clip_extents.width, clip_extents.height ), &real_bounds, &real_bounds); } node = gsk_color_node_new (color, &real_bounds); if (name && snapshot->record_names) { va_list args; char *str; va_start (args, name); str = g_strdup_vprintf (name, args); va_end (args); gsk_render_node_set_name (node, str); g_free (str); } gtk_snapshot_append_node (snapshot, node); gsk_render_node_unref (node); } /** * gtk_snapshot_clips_rect: * @snapshot: a #GtkSnapshot * @bounds: a rectangle * * Tests whether the rectangle is entirely outside the clip region of @snapshot. * * Returns: %TRUE if @bounds is entirely outside the clip region */ gboolean gtk_snapshot_clips_rect (GtkSnapshot *snapshot, const cairo_rectangle_int_t *rect) { const GtkSnapshotState *current_state = gtk_snapshot_get_current_state (snapshot); cairo_rectangle_int_t offset_rect; if (current_state->clip_region == NULL) return FALSE; offset_rect.x = rect->x + current_state->translate_x; offset_rect.y = rect->y + current_state->translate_y; offset_rect.width = rect->width; offset_rect.height = rect->height; return cairo_region_contains_rectangle (current_state->clip_region, &offset_rect) == CAIRO_REGION_OVERLAP_OUT; } /** * 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, gdouble x, gdouble y, gdouble width, gdouble height) { g_return_if_fail (snapshot != NULL); g_return_if_fail (GTK_IS_STYLE_CONTEXT (context)); gtk_snapshot_offset (snapshot, x, y); gtk_css_style_snapshot_background (gtk_style_context_lookup_style (context), snapshot, width, height); gtk_snapshot_offset (snapshot, -x, -y); } /** * 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, gdouble x, gdouble y, gdouble width, gdouble height) { g_return_if_fail (snapshot != NULL); g_return_if_fail (GTK_IS_STYLE_CONTEXT (context)); gtk_snapshot_offset (snapshot, x, y); gtk_css_style_snapshot_border (gtk_style_context_lookup_style (context), snapshot, width, height); gtk_snapshot_offset (snapshot, -x, -y); } /** * 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, gdouble x, gdouble y, gdouble width, gdouble height) { g_return_if_fail (snapshot != NULL); g_return_if_fail (GTK_IS_STYLE_CONTEXT (context)); gtk_snapshot_offset (snapshot, x, y); gtk_css_style_snapshot_outline (gtk_style_context_lookup_style (context), snapshot, width, height); gtk_snapshot_offset (snapshot, -x, -y); } /** * 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, gdouble x, gdouble y, PangoLayout *layout) { 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)); gtk_snapshot_offset (snapshot, x, y); fg_color = _gtk_css_rgba_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_shadows_value_push_snapshot (shadows_value, snapshot); gsk_pango_show_layout (snapshot, fg_color, layout); if (has_shadow) gtk_snapshot_pop (snapshot); gtk_snapshot_offset (snapshot, -x, -y); } /* * gtk_snapshot_append_linear_gradient: * @snapshot: a #GtkSnapshot * @bounds: the rectangle to render the linear gradient into * @start: the point at which the linear gradient will begin * @end: the point at which the linear gradient will finish * @stops: (array length=n_stops): a pointer to an array of #GskColorStop defining the gradient * @n_stops: the number of elements in @color_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, const char *name, ...) { const GtkSnapshotState *current_state = gtk_snapshot_get_current_state (snapshot); GskRenderNode *node; graphene_rect_t real_bounds; graphene_point_t real_start_point; graphene_point_t real_end_point; 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); graphene_rect_offset_r (bounds, current_state->translate_x, current_state->translate_y, &real_bounds); real_start_point.x = start_point->x + current_state->translate_x; real_start_point.y = start_point->y + current_state->translate_y; real_end_point.x = end_point->x + current_state->translate_x; real_end_point.y = end_point->y + current_state->translate_y; /* Linear gradients can be trivially clipped if we don't change the start/end points. */ if (current_state->clip_region) { cairo_rectangle_int_t clip_extents; cairo_region_get_extents (current_state->clip_region, &clip_extents); graphene_rect_intersection (&GRAPHENE_RECT_INIT ( clip_extents.x, clip_extents.y, clip_extents.width, clip_extents.height ), &real_bounds, &real_bounds); } node = gsk_linear_gradient_node_new (&real_bounds, &real_start_point, &real_end_point, stops, n_stops); if (name && snapshot->record_names) { va_list args; char *str; va_start (args, name); str = g_strdup_vprintf (name, args); va_end (args); gsk_render_node_set_name (node, str); g_free (str); } gtk_snapshot_append_node (snapshot, node); gsk_render_node_unref (node); } /* * gtk_snapshot_append_repeating_linear_gradient: * @snapshot: a #GtkSnapshot * @bounds: the rectangle to render the linear gradient into * @start: the point at which the linear gradient will begin * @end: the point at which the linear gradient will finish * @stops: (array length=n_stops): a pointer to an array of #GskColorStop defining the gradient * @n_stops: the number of elements in @color_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, const char *name, ...) { const GtkSnapshotState *current_state = gtk_snapshot_get_current_state (snapshot); GskRenderNode *node; graphene_rect_t real_bounds; graphene_point_t real_start_point; graphene_point_t real_end_point; 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); graphene_rect_offset_r (bounds, current_state->translate_x, current_state->translate_y, &real_bounds); real_start_point.x = start_point->x + current_state->translate_x; real_start_point.y = start_point->y + current_state->translate_y; real_end_point.x = end_point->x + current_state->translate_x; real_end_point.y = end_point->y + current_state->translate_y; /* Repeating Linear gradients can be trivially clipped if we don't change the start/end points. */ if (current_state->clip_region) { cairo_rectangle_int_t clip_extents; cairo_region_get_extents (current_state->clip_region, &clip_extents); graphene_rect_intersection (&GRAPHENE_RECT_INIT ( clip_extents.x, clip_extents.y, clip_extents.width, clip_extents.height ), &real_bounds, &real_bounds); } node = gsk_repeating_linear_gradient_node_new (&real_bounds, &real_start_point, &real_end_point, stops, n_stops); if (name && snapshot->record_names) { va_list args; char *str; va_start (args, name); str = g_strdup_vprintf (name, args); va_end (args); gsk_render_node_set_name (node, str); g_free (str); } gtk_snapshot_append_node (snapshot, node); gsk_render_node_unref (node); }