diff options
Diffstat (limited to 'gsk/gskrendernode.c')
-rw-r--r-- | gsk/gskrendernode.c | 1246 |
1 files changed, 1246 insertions, 0 deletions
diff --git a/gsk/gskrendernode.c b/gsk/gskrendernode.c new file mode 100644 index 0000000000..092b96a2e3 --- /dev/null +++ b/gsk/gskrendernode.c @@ -0,0 +1,1246 @@ +/* GSK - The GTK Scene Kit + * + * Copyright 2016 Endless + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + +/** + * SECTION:GskRenderNode + * @title: GskRenderNode + * @Short_desc: Simple scene graph element + * + * TODO + */ + +#include "config.h" + +#include "gskrendernodeprivate.h" + +#include "gskdebugprivate.h" +#include "gskrendernodeiter.h" + +#include <graphene-gobject.h> + +G_DEFINE_TYPE (GskRenderNode, gsk_render_node, G_TYPE_OBJECT) + +static void +gsk_render_node_dispose (GObject *gobject) +{ + GskRenderNode *self = GSK_RENDER_NODE (gobject); + GskRenderNodeIter iter; + + gsk_render_node_set_invalidate_func (self, NULL, NULL, NULL); + + gsk_render_node_iter_init (&iter, self); + while (gsk_render_node_iter_next (&iter, NULL)) + gsk_render_node_iter_remove (&iter); + + G_OBJECT_CLASS (gsk_render_node_parent_class)->dispose (gobject); +} + +static void +gsk_render_node_real_resize (GskRenderNode *node) +{ +} + +static void +gsk_render_node_class_init (GskRenderNodeClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->dispose = gsk_render_node_dispose; + + klass->resize = gsk_render_node_real_resize; +} + +static void +gsk_render_node_init (GskRenderNode *self) +{ + graphene_rect_init_from_rect (&self->bounds, graphene_rect_zero ()); + + graphene_matrix_init_identity (&self->transform); + graphene_matrix_init_identity (&self->child_transform); + + self->opacity = 1.0; +} + +/** + * gsk_render_node_new: + * + * Creates a new #GskRenderNode, to be used with #GskRenderer. + * + * Returns: (transfer full): the newly created #GskRenderNode + * + * Since: 3.22 + */ +GskRenderNode * +gsk_render_node_new (void) +{ + return g_object_new (GSK_TYPE_RENDER_NODE, NULL); +} + +/** + * gsk_render_node_get_parent: + * @node: a #GskRenderNode + * + * Returns the parent of the @node. + * + * Returns: (transfer none): the parent of the #GskRenderNode + * + * Since: 3.22 + */ +GskRenderNode * +gsk_render_node_get_parent (GskRenderNode *node) +{ + g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL); + + return node->parent; +} + +/** + * gsk_render_node_get_first_child: + * @node: a #GskRenderNode + * + * Returns the first child of @node. + * + * Returns: (transfer none): the first child of the #GskRenderNode + * + * Since: 3.22 + */ +GskRenderNode * +gsk_render_node_get_first_child (GskRenderNode *node) +{ + g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL); + + return node->first_child; +} + +/** + * gsk_render_node_get_last_child: + * + * Returns the last child of @node. + * + * Returns: (transfer none): the last child of the #GskRenderNode + * + * Since: 3.22 + */ +GskRenderNode * +gsk_render_node_get_last_child (GskRenderNode *node) +{ + g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL); + + return node->last_child; +} + +/** + * gsk_render_node_get_next_sibling: + * + * Returns the next sibling of @node. + * + * Returns: (transfer none): the next sibling of the #GskRenderNode + * + * Since: 3.22 + */ +GskRenderNode * +gsk_render_node_get_next_sibling (GskRenderNode *node) +{ + g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL); + + return node->next_sibling; +} + +/** + * gsk_render_node_get_previous_sibling: + * + * Returns the previous sibling of @node. + * + * Returns: (transfer none): the previous sibling of the #GskRenderNode + * + * Since: 3.22 + */ +GskRenderNode * +gsk_render_node_get_previous_sibling (GskRenderNode *node) +{ + g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL); + + return node->prev_sibling; +} + +typedef void (* InsertChildFunc) (GskRenderNode *node, + GskRenderNode *child, + gpointer user_data); + +static void +gsk_render_node_insert_child_internal (GskRenderNode *node, + GskRenderNode *child, + InsertChildFunc insert_func, + gpointer insert_func_data) +{ + if (node == child) + { + g_critical ("The render node of type '%s' cannot be added to itself.", + G_OBJECT_TYPE_NAME (node)); + return; + } + + if (child->parent != NULL) + { + g_critical ("The render node of type '%s' already has a parent of type '%s'; " + "render nodes cannot be added to multiple parents.", + G_OBJECT_TYPE_NAME (child), + G_OBJECT_TYPE_NAME (node)); + return; + } + + insert_func (node, child, insert_func_data); + + g_object_ref (child); + + child->parent = node; + child->age = 0; + child->needs_world_matrix_update = TRUE; + + node->n_children += 1; + node->age += 1; + node->needs_world_matrix_update = TRUE; + + /* Transfer invalidated children to the current top-level */ + if (child->invalidated_descendants != NULL) + { + if (node->parent == NULL) + node->invalidated_descendants = child->invalidated_descendants; + else + { + GskRenderNode *tmp = gsk_render_node_get_toplevel (node); + + tmp->invalidated_descendants = child->invalidated_descendants; + } + + child->invalidated_descendants = NULL; + } + + if (child->prev_sibling == NULL) + node->first_child = child; + if (child->next_sibling == NULL) + node->last_child = child; +} + +static void +insert_child_at_pos (GskRenderNode *node, + GskRenderNode *child, + gpointer user_data) +{ + int pos = GPOINTER_TO_INT (user_data); + + if (pos == 0) + { + GskRenderNode *tmp = node->first_child; + + if (tmp != NULL) + tmp->prev_sibling = child; + + child->prev_sibling = NULL; + child->next_sibling = tmp; + + return; + } + + if (pos < 0 || pos >= node->n_children) + { + GskRenderNode *tmp = node->last_child; + + if (tmp != NULL) + tmp->next_sibling = child; + + child->prev_sibling = tmp; + child->next_sibling = NULL; + + return; + } + + { + GskRenderNode *iter; + int i; + + for (iter = node->first_child, i = 0; + iter != NULL; + iter = iter->next_sibling, i++) + { + if (i == pos) + { + GskRenderNode *tmp = iter->prev_sibling; + + child->prev_sibling = tmp; + child->next_sibling = iter; + + iter->prev_sibling = child; + + if (tmp != NULL) + tmp->next_sibling = child; + + break; + } + } + } +} + +/** + * gsk_render_node_insert_child_at_pos: + * @node: a #GskRenderNode + * @child: a #GskRenderNode + * @index_: the index in the list of children where @child should be inserted at + * + * Inserts @child into the list of children of @node, using the given @index_. + * + * If @index_ is 0, the @child will be prepended to the list of children. + * + * If @index_ is less than zero, or equal to the number of children, the @child + * will be appended to the list of children. + * + * This function acquires a reference on @child. + * + * Returns: (transfer none): the #GskRenderNode + * + * Since: 3.22 + */ +GskRenderNode * +gsk_render_node_insert_child_at_pos (GskRenderNode *node, + GskRenderNode *child, + int index_) +{ + g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL); + g_return_val_if_fail (GSK_IS_RENDER_NODE (child), node); + + gsk_render_node_insert_child_internal (node, child, + insert_child_at_pos, + GINT_TO_POINTER (index_)); + + return node; +} + +static void +insert_child_before (GskRenderNode *node, + GskRenderNode *child, + gpointer user_data) +{ + GskRenderNode *sibling = user_data; + + if (sibling == NULL) + sibling = node->first_child; + + child->next_sibling = sibling; + + if (sibling != NULL) + { + GskRenderNode *tmp = sibling->prev_sibling; + + child->prev_sibling = tmp; + + if (tmp != NULL) + tmp->next_sibling = child; + + sibling->prev_sibling = child; + } + else + child->prev_sibling = NULL; +} + +/** + * gsk_render_node_insert_child_before: + * @node: a #GskRenderNode + * @child: a #GskRenderNode + * @sibling: (nullable): a #GskRenderNode, or %NULL + * + * Inserts @child in the list of children of @node, before @sibling. + * + * If @sibling is %NULL, the @child will be inserted at the beginning of the + * list of children. + * + * This function acquires a reference of @child. + * + * Returns: (transfer none): the #GskRenderNode + * + * Since: 3.22 + */ +GskRenderNode * +gsk_render_node_insert_child_before (GskRenderNode *node, + GskRenderNode *child, + GskRenderNode *sibling) +{ + g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL); + g_return_val_if_fail (GSK_IS_RENDER_NODE (child), node); + g_return_val_if_fail (sibling == NULL || GSK_IS_RENDER_NODE (sibling), node); + + gsk_render_node_insert_child_internal (node, child, insert_child_before, sibling); + + return node; +} + +static void +insert_child_after (GskRenderNode *node, + GskRenderNode *child, + gpointer user_data) +{ + GskRenderNode *sibling = user_data; + + if (sibling == NULL) + sibling = node->last_child; + + child->prev_sibling = sibling; + + if (sibling != NULL) + { + GskRenderNode *tmp = sibling->next_sibling; + + child->next_sibling = tmp; + + if (tmp != NULL) + tmp->prev_sibling = child; + + sibling->next_sibling = child; + } + else + child->next_sibling = NULL; +} + +/** + * gsk_render_node_insert_child_after: + * @node: a #GskRenderNode + * @child: a #GskRenderNode + * @sibling: (nullable): a #GskRenderNode, or %NULL + * + * Inserts @child in the list of children of @node, after @sibling. + * + * If @sibling is %NULL, the @child will be inserted at the end of the list + * of children. + * + * This function acquires a reference of @child. + * + * Returns: (transfer none): the #GskRenderNode + * + * Since: 3.22 + */ +GskRenderNode * +gsk_render_node_insert_child_after (GskRenderNode *node, + GskRenderNode *child, + GskRenderNode *sibling) +{ + g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL); + g_return_val_if_fail (GSK_IS_RENDER_NODE (child), node); + g_return_val_if_fail (sibling == NULL || GSK_IS_RENDER_NODE (sibling), node); + + if (sibling != NULL) + g_return_val_if_fail (sibling->parent == node, node); + + gsk_render_node_insert_child_internal (node, child, insert_child_after, sibling); + + return node; +} + +typedef struct { + GskRenderNode *prev_sibling; + GskRenderNode *next_sibling; +} InsertBetween; + +static void +insert_child_between (GskRenderNode *node, + GskRenderNode *child, + gpointer data_) +{ + InsertBetween *data = data_; + + child->prev_sibling = data->prev_sibling; + child->next_sibling = data->next_sibling; + + if (data->prev_sibling != NULL) + data->prev_sibling->next_sibling = child; + + if (data->next_sibling != NULL) + data->next_sibling->prev_sibling = child; +} + +/** + * gsk_render_node_replace_child: + * @node: a #GskRenderNode + * @new_child: the #GskRenderNode to add + * @old_child: the #GskRenderNode to replace + * + * Replaces @old_child with @new_child in the list of children of @node. + * + * This function acquires a reference to @new_child, and releases a reference + * of @old_child. + * + * Returns: (transfer none): the #GskRenderNode + * + * Since: 3.22 + */ +GskRenderNode * +gsk_render_node_replace_child (GskRenderNode *node, + GskRenderNode *new_child, + GskRenderNode *old_child) +{ + InsertBetween clos; + + g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL); + g_return_val_if_fail (GSK_IS_RENDER_NODE (new_child), node); + g_return_val_if_fail (GSK_IS_RENDER_NODE (old_child), node); + + g_return_val_if_fail (new_child->parent == NULL, node); + g_return_val_if_fail (old_child->parent == node, node); + + clos.prev_sibling = old_child->prev_sibling; + clos.next_sibling = old_child->next_sibling; + gsk_render_node_remove_child (node, old_child); + + gsk_render_node_insert_child_internal (node, new_child, insert_child_between, &clos); + + return node; +} + +/** + * gsk_render_node_remove_child: + * @node: a #GskRenderNode + * @child: a #GskRenderNode child of @node + * + * Removes @child from the list of children of @node. + * + * This function releases the reference acquired when adding @child to the + * list of children. + * + * Returns: (transfer none): the #GskRenderNode + */ +GskRenderNode * +gsk_render_node_remove_child (GskRenderNode *node, + GskRenderNode *child) +{ + GskRenderNode *prev_sibling, *next_sibling; + + g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL); + g_return_val_if_fail (GSK_IS_RENDER_NODE (child), node); + + if (child->parent != node) + { + g_critical ("The render node of type '%s' is not a child of the render node of type '%s'", + G_OBJECT_TYPE_NAME (child), + G_OBJECT_TYPE_NAME (node)); + return node; + } + + prev_sibling = child->prev_sibling; + next_sibling = child->next_sibling; + + child->parent = NULL; + child->prev_sibling = NULL; + child->next_sibling = NULL; + child->age = 0; + + if (prev_sibling) + prev_sibling->next_sibling = next_sibling; + if (next_sibling) + next_sibling->prev_sibling = prev_sibling; + + node->age += 1; + node->n_children -= 1; + + if (node->first_child == child) + node->first_child = next_sibling; + if (node->last_child == child) + node->last_child = prev_sibling; + + g_object_unref (child); + + return node; +} + +/** + * gsk_render_node_remove_all_children: + * @node: a #GskRenderNode + * + * Removes all children of @node. + * + * See also: gsk_render_node_remove_child() + * + * Returns: (transfer none): the #GskRenderNode + * + * Since: 3.22 + */ +GskRenderNode * +gsk_render_node_remove_all_children (GskRenderNode *node) +{ + GskRenderNodeIter iter; + + g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL); + + if (node->n_children == 0) + return node; + + gsk_render_node_iter_init (&iter, node); + while (gsk_render_node_iter_next (&iter, NULL)) + gsk_render_node_iter_remove (&iter); + + g_assert (node->n_children == 0); + g_assert (node->first_child == NULL); + g_assert (node->last_child == NULL); + + return node; +} + +/** + * gsk_render_node_get_n_children: + * @node: a #GskRenderNode + * + * Retrieves the number of direct children of @node. + * + * Returns: the number of children of the #GskRenderNode + * + * Since: 3.22 + */ +guint +gsk_render_node_get_n_children (GskRenderNode *node) +{ + g_return_val_if_fail (GSK_IS_RENDER_NODE (node), 0); + + return node->n_children; +} + +/** + * gsk_render_node_set_bounds: + * @node: a #GskRenderNode + * @bounds: (nullable): the boundaries of @node + * + * Sets the boundaries of @node, which describe the geometry of the + * render node, and are used to clip the surface associated to it + * when rendering. + * + * Since: 3.22 + */ +void +gsk_render_node_set_bounds (GskRenderNode *node, + const graphene_rect_t *bounds) +{ + g_return_if_fail (GSK_IS_RENDER_NODE (node)); + + if (bounds == NULL) + graphene_rect_init_from_rect (&node->bounds, graphene_rect_zero ()); + else + graphene_rect_init_from_rect (&node->bounds, bounds); + + gsk_render_node_queue_invalidate (node, GSK_RENDER_NODE_CHANGES_UPDATE_BOUNDS); +} + +/** + * gsk_render_node_get_bounds: + * @node: a #GskRenderNode + * @bounds: (out caller-allocates): return location for the boundaries + * + * Retrieves the boundaries set using gsk_render_node_set_bounds(). + * + * Since: 3.22 + */ +void +gsk_render_node_get_bounds (GskRenderNode *node, + graphene_rect_t *bounds) +{ + g_return_if_fail (GSK_IS_RENDER_NODE (node)); + g_return_if_fail (bounds != NULL); + + *bounds = node->bounds; +} + +/** + * gsk_render_node_set_transform: + * @node: a #GskRenderNode + * @transform: (nullable): a transformation matrix + * + * Sets the transformation matrix used when rendering the @node. + * + * Since: 3.22 + */ +void +gsk_render_node_set_transform (GskRenderNode *node, + const graphene_matrix_t *transform) +{ + g_return_if_fail (GSK_IS_RENDER_NODE (node)); + + if (transform == NULL) + graphene_matrix_init_identity (&node->transform); + else + graphene_matrix_init_from_matrix (&node->transform, transform); + + node->transform_set = !graphene_matrix_is_identity (&node->transform); + gsk_render_node_queue_invalidate (node, GSK_RENDER_NODE_CHANGES_UPDATE_TRANSFORM); +} + +/** + * gsk_render_node_set_child_transform: + * @node: a #GskRenderNode + * @transform: (nullable): a transformation matrix + * + * Sets the transformation matrix used when rendering the children + * of @node. + * + * Since: 3.22 + */ +void +gsk_render_node_set_child_transform (GskRenderNode *node, + const graphene_matrix_t *transform) +{ + GskRenderNodeIter iter; + GskRenderNode *child; + + g_return_if_fail (GSK_IS_RENDER_NODE (node)); + + if (transform == NULL) + graphene_matrix_init_identity (&node->child_transform); + else + graphene_matrix_init_from_matrix (&node->child_transform, transform); + + node->child_transform_set = !graphene_matrix_is_identity (&node->child_transform); + + /* We need to invalidate the world matrix for our children */ + gsk_render_node_iter_init (&iter, node); + while (gsk_render_node_iter_next (&iter, &child)) + gsk_render_node_queue_invalidate (child, GSK_RENDER_NODE_CHANGES_UPDATE_TRANSFORM); +} + +/** + * gsk_render_node_set_opacity: + * @node: a #GskRenderNode + * @opacity: the opacity of the node, between 0 (fully transparent) and + * 1 (fully opaque) + * + * Sets the opacity of the @node. + * + * Since: 3.22 + */ +void +gsk_render_node_set_opacity (GskRenderNode *node, + double opacity) +{ + g_return_if_fail (GSK_IS_RENDER_NODE (node)); + + node->opacity = CLAMP (opacity, 0.0, 1.0); + + gsk_render_node_queue_invalidate (node, GSK_RENDER_NODE_CHANGES_UPDATE_OPACITY); +} + +/** + * gsk_render_node_get_opacity: + * @node: a #GskRenderNode + * + * Retrieves the opacity set using gsk_render_node_set_opacity(). + * + * Returns: the opacity of the #GskRenderNode + * + * Since: 3.22 + */ +double +gsk_render_node_get_opacity (GskRenderNode *node) +{ + g_return_val_if_fail (GSK_IS_RENDER_NODE (node), 0.0); + + return node->opacity; +} + +/** + * gsk_render_node_set_hidden: + * @node: a #GskRenderNode + * @hidden: whether the @node should be hidden or not + * + * Sets whether the @node should be hidden. + * + * Hidden nodes, and their descendants, are not rendered. + * + * Since: 3.22 + */ +void +gsk_render_node_set_hidden (GskRenderNode *node, + gboolean hidden) +{ + g_return_if_fail (GSK_IS_RENDER_NODE (node)); + + node->hidden = !!hidden; + + gsk_render_node_queue_invalidate (node, GSK_RENDER_NODE_CHANGES_UPDATE_VISIBILITY); +} + +/** + * gsk_render_node_is_hidden: + * @node: a #GskRenderNode + * + * Checks whether a @node is hidden. + * + * Returns: %TRUE if the #GskRenderNode is hidden + * + * Since: 3.22 + */ +gboolean +gsk_render_node_is_hidden (GskRenderNode *node) +{ + g_return_val_if_fail (GSK_IS_RENDER_NODE (node), TRUE); + + return node->hidden; +} + +/** + * gsk_render_node_set_opaque: + * @node: a #GskRenderNode + * @opaque: whether the node is fully opaque or not + * + * Sets whether the node is known to be fully opaque. + * + * Fully opaque nodes will ignore the opacity set using gsk_render_node_set_opacity(), + * but if their parent is not opaque they may still be rendered with an opacity. + * + * Renderers may use this information to optimize the rendering pipeline. + * + * Since: 3.22 + */ +void +gsk_render_node_set_opaque (GskRenderNode *node, + gboolean opaque) +{ + g_return_if_fail (GSK_IS_RENDER_NODE (node)); + + node->opaque = !!opaque; + + gsk_render_node_queue_invalidate (node, GSK_RENDER_NODE_CHANGES_UPDATE_OPACITY); +} + +/** + * gsk_render_node_is_opaque: + * @node: a #GskRenderNode + * + * Retrieves the value set using gsk_render_node_set_opaque(). + * + * Returns: %TRUE if the #GskRenderNode is fully opaque + * + * Since: 3.22 + */ +gboolean +gsk_render_node_is_opaque (GskRenderNode *node) +{ + g_return_val_if_fail (GSK_IS_RENDER_NODE (node), TRUE); + + return node->opaque; +} + +/** + * gsk_render_node_contains: + * @node: a #GskRenderNode + * @descendant: a #GskRenderNode + * + * Checks whether @node contains @descendant. + * + * Returns: %TRUE if the #GskRenderNode contains the given + * descendant + * + * Since: 3.22 + */ +gboolean +gsk_render_node_contains (GskRenderNode *node, + GskRenderNode *descendant) +{ + GskRenderNode *tmp; + + g_return_val_if_fail (GSK_IS_RENDER_NODE (node), FALSE); + g_return_val_if_fail (GSK_IS_RENDER_NODE (descendant), FALSE); + + for (tmp = descendant; tmp != NULL; tmp = tmp->parent) + if (tmp == node) + return TRUE; + + return FALSE; +} + +/** + * gsk_render_node_set_surface: + * @node: a #GskRenderNode + * @surface: (nullable): a Cairo surface + * + * Sets the contents of the #GskRenderNode. + * + * The @node will acquire a reference on the given @surface. + * + * Since: 3.22 + */ +void +gsk_render_node_set_surface (GskRenderNode *node, + cairo_surface_t *surface) +{ + g_return_if_fail (GSK_IS_RENDER_NODE (node)); + + g_clear_pointer (&node->surface, cairo_surface_destroy); + + if (surface != NULL) + node->surface = cairo_surface_reference (surface); + + gsk_render_node_queue_invalidate (node, GSK_RENDER_NODE_CHANGES_UPDATE_SURFACE); +} + +/*< private > + * gsk_render_node_get_toplevel: + * @node: a #GskRenderNode + * + * Retrieves the top level #GskRenderNode without a parent. + * + * Returns: (transfer none): the top level #GskRenderNode + */ +GskRenderNode * +gsk_render_node_get_toplevel (GskRenderNode *node) +{ + GskRenderNode *parent; + + parent = node->parent; + if (parent == NULL) + return node; + + while (parent != NULL) + { + if (parent->parent == NULL) + return parent; + + parent = parent->parent; + } + + return NULL; +} + +/*< private > + * gsk_render_node_update_world_matrix: + * @node: a #GskRenderNode + * @force: %TRUE if the update should be forced + * + * Updates the cached world matrix of @node and its children, if needed. + */ +void +gsk_render_node_update_world_matrix (GskRenderNode *node, + gboolean force) +{ + GskRenderNodeIter iter; + GskRenderNode *child; + + if (force || node->needs_world_matrix_update) + { + GSK_NOTE (RENDER_NODE, g_print ("Updating cached world matrix on node %p [parent=%p, t_set=%s, ct_set=%s]\n", + node, + node->parent != NULL ? node->parent : 0, + node->transform_set ? "y" : "n", + node->parent != NULL && node->parent->child_transform_set ? "y" : "n")); + + if (node->parent == NULL) + { + if (node->transform_set) + graphene_matrix_init_from_matrix (&node->world_matrix, &node->transform); + else + graphene_matrix_init_identity (&node->world_matrix); + } + else + { + GskRenderNode *parent = node->parent; + graphene_matrix_t tmp; + + if (parent->child_transform_set) + graphene_matrix_init_from_matrix (&tmp, &parent->child_transform); + else + graphene_matrix_init_identity (&tmp); + + if (node->transform_set) + graphene_matrix_multiply (&tmp, &node->transform, &tmp); + + graphene_matrix_multiply (&tmp, &parent->world_matrix, &node->world_matrix); + } + + node->needs_world_matrix_update = FALSE; + } + + gsk_render_node_iter_init (&iter, node); + while (gsk_render_node_iter_next (&iter, &child)) + gsk_render_node_update_world_matrix (child, TRUE); +} + +/*< private > + * gsk_render_node_get_surface: + * @node: a #GskRenderNode + * + * Retrieves the surface set using gsk_render_node_set_surface(). + * + * Returns: (transfer none) (nullable): a Cairo surface + */ +cairo_surface_t * +gsk_render_node_get_surface (GskRenderNode *node) +{ + g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL); + + return node->surface; +} + +/*< private > + * gsk_render_node_get_world_matrix: + * @node: a #GskRenderNode + * @mv: (out caller-allocates): return location for the modelview matrix + * in world-relative coordinates + * + * Retrieves the modelview matrix in world-relative coordinates. + */ +void +gsk_render_node_get_world_matrix (GskRenderNode *node, + graphene_matrix_t *mv) +{ + g_return_if_fail (GSK_IS_RENDER_NODE (node)); + g_return_if_fail (mv != NULL); + + if (node->needs_world_matrix_update) + { + GskRenderNode *tmp = gsk_render_node_get_toplevel (node); + + gsk_render_node_update_world_matrix (tmp, TRUE); + + g_assert (!node->needs_world_matrix_update); + } + + *mv = node->world_matrix; +} + +void +gsk_render_node_set_invalidate_func (GskRenderNode *node, + GskRenderNodeInvalidateFunc invalidate_func, + gpointer func_data, + GDestroyNotify destroy_func_data) +{ + if (node->parent != NULL) + { + g_critical ("Render node of type '%s' is not a root node. Only root " + "nodes can have an invalidation function.", + G_OBJECT_TYPE_NAME (node)); + return; + } + + if (node->invalidate_func != NULL) + { + if (node->destroy_func_data != NULL) + node->destroy_func_data (node->func_data); + } + + node->invalidate_func = invalidate_func; + node->func_data = func_data; + node->destroy_func_data = destroy_func_data; +} + +GskRenderNodeChanges +gsk_render_node_get_current_state (GskRenderNode *node) +{ + GskRenderNodeChanges res = 0; + + if (node->needs_resize) + res |= GSK_RENDER_NODE_CHANGES_UPDATE_BOUNDS; + if (node->needs_world_matrix_update) + res |= GSK_RENDER_NODE_CHANGES_UPDATE_TRANSFORM; + if (node->needs_content_update) + res |= GSK_RENDER_NODE_CHANGES_UPDATE_SURFACE; + if (node->needs_opacity_update) + res |= GSK_RENDER_NODE_CHANGES_UPDATE_OPACITY; + if (node->needs_visibility_update) + res |= GSK_RENDER_NODE_CHANGES_UPDATE_VISIBILITY; + + return res; +} + +GskRenderNodeChanges +gsk_render_node_get_last_state (GskRenderNode *node) +{ + return node->last_state_change; +} + +void +gsk_render_node_queue_invalidate (GskRenderNode *node, + GskRenderNodeChanges changes) +{ + GskRenderNodeChanges cur_invalidated_bits = 0; + GskRenderNode *root; + int i; + + cur_invalidated_bits = gsk_render_node_get_current_state (node); + if ((cur_invalidated_bits & changes) != 0) + return; + + node->needs_resize = (changes & GSK_RENDER_NODE_CHANGES_UPDATE_BOUNDS) != 0; + node->needs_world_matrix_update = (changes & GSK_RENDER_NODE_CHANGES_UPDATE_TRANSFORM) != 0; + node->needs_content_update = (changes & GSK_RENDER_NODE_CHANGES_UPDATE_SURFACE) != 0; + node->needs_opacity_update = (changes & GSK_RENDER_NODE_CHANGES_UPDATE_OPACITY) != 0; + node->needs_visibility_update = (changes & GSK_RENDER_NODE_CHANGES_UPDATE_VISIBILITY) != 0; + + if (node->parent == NULL) + { + GSK_NOTE (RENDER_NODE, g_print ("Invalid node [%p] is top-level\n", node)); + return; + } + + root = gsk_render_node_get_toplevel (node); + + if (root->invalidated_descendants == NULL) + root->invalidated_descendants = g_ptr_array_new (); + + for (i = 0; i < root->invalidated_descendants->len; i++) + { + if (node == g_ptr_array_index (root->invalidated_descendants, i)) + { + GSK_NOTE (RENDER_NODE, g_print ("Node [%p] already invalidated; skipping...\n", node)); + return; + } + } + + GSK_NOTE (RENDER_NODE, g_print ("Adding node [%p] to list of invalid descendants of [%p]\n", node, root)); + g_ptr_array_add (root->invalidated_descendants, node); +} + +void +gsk_render_node_validate (GskRenderNode *node) +{ + GPtrArray *invalidated_descendants; + gboolean call_invalidate_func; + int i; + + node->last_state_change = gsk_render_node_get_current_state (node); + + /* We call the invalidation function if our state changed, or if + * the descendants state has changed + */ + call_invalidate_func = node->last_state_change != 0 || + node->invalidated_descendants != NULL; + + gsk_render_node_maybe_resize (node); + gsk_render_node_update_world_matrix (node, FALSE); + node->needs_content_update = FALSE; + node->needs_visibility_update = FALSE; + node->needs_opacity_update = FALSE; + + /* Steal the array of invalidated descendants, so that changes caused by + * the validation will not cause recursions + */ + invalidated_descendants = node->invalidated_descendants; + node->invalidated_descendants = NULL; + + if (invalidated_descendants != NULL) + { + for (i = 0; i < invalidated_descendants->len; i++) + { + GskRenderNode *child = g_ptr_array_index (invalidated_descendants, i); + + child->last_state_change = 0; + + GSK_NOTE (RENDER_NODE, g_print ("Validating descendant node [%p] (resize:%s, transform:%s)\n", + child, + child->needs_resize ? "yes" : "no", + child->needs_world_matrix_update ? "yes" : "no")); + + child->last_state_change = gsk_render_node_get_current_state (child); + + gsk_render_node_maybe_resize (child); + gsk_render_node_update_world_matrix (child, FALSE); + + child->needs_content_update = FALSE; + child->needs_visibility_update = FALSE; + child->needs_opacity_update = FALSE; + } + } + + g_clear_pointer (&invalidated_descendants, g_ptr_array_unref); + + if (call_invalidate_func && node->invalidate_func != NULL) + node->invalidate_func (node, node->func_data); +} + +void +gsk_render_node_maybe_resize (GskRenderNode *node) +{ + g_return_if_fail (GSK_IS_RENDER_NODE (node)); + + if (!node->needs_resize) + return; + + GSK_RENDER_NODE_GET_CLASS (node)->resize (node); + + node->needs_resize = FALSE; +} + +/** + * gsk_render_node_set_name: + * @node: a #GskRenderNode + * @name: (nullable): a name for the node + * + * Sets the name of the node. + * + * A name is generally useful for debugging purposes. + * + * Since: 3.22 + */ +void +gsk_render_node_set_name (GskRenderNode *node, + const char *name) +{ + g_return_if_fail (GSK_IS_RENDER_NODE (node)); + + g_free (node->name); + node->name = g_strdup (name); +} + +static cairo_user_data_key_t render_node_context_key; + +static void +surface_invalidate (void *data) +{ + GskRenderNode *node = data; + + gsk_render_node_queue_invalidate (node, GSK_RENDER_NODE_CHANGES_UPDATE_SURFACE); +} + +/** + * gsk_render_node_get_draw_context: + * @node: a #GskRenderNode + * + * Creates a Cairo context for drawing using the surface associated + * to the render node. If no surface has been attached to the render + * node, a new surface will be created as a side effect. + * + * Returns: (transfer full): a Cairo context used for drawing; use + * cairo_destroy() when done drawing + * + * Since: 3.22 + */ +cairo_t * +gsk_render_node_get_draw_context (GskRenderNode *node) +{ + cairo_t *res; + + g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL); + + if (node->surface == NULL) + node->surface = cairo_image_surface_create (node->opaque ? CAIRO_FORMAT_RGB24 + : CAIRO_FORMAT_ARGB32, + node->bounds.size.width, + node->bounds.size.height); + + res = cairo_create (node->surface); + + cairo_rectangle (res, + node->bounds.origin.x, node->bounds.origin.y, + node->bounds.size.width, node->bounds.size.height); + cairo_clip (res); + + cairo_set_user_data (res, &render_node_context_key, node, surface_invalidate); + + return res; +} |