/* 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 .
*/
/**
* SECTION:GskRenderNode
* @Title: GskRenderNode
* @Short_description: Simple scene graph element
*
* #GskRenderNode is the basic block in a scene graph to be
* rendered using #GskRenderer.
*
* Each node has a parent, except the top-level node; each node may have
* children nodes.
*
* Each node has an associated drawing surface, which has the size of
* the rectangle set using gsk_render_node_set_bounds(). Nodes have an
* associated transformation matrix, which is used to position and
* transform the node on the scene graph; additionally, they also have
* a child transformation matrix, which will be applied to each child.
*
* Render nodes are meant to be transient; once they have been associated
* to a #GskRenderer it's safe to release any reference you have on them.
* Once a #GskRenderNode has been rendered, it is marked as immutable, and
* cannot be modified.
*/
#include "config.h"
#include "gskrendernodeprivate.h"
#include "gskdebugprivate.h"
#include "gskrendernodeiter.h"
#include "gskrendererprivate.h"
#include "gsktexture.h"
#include
#include
#include
/**
* GskRenderNode: (ref-func gsk_render_node_ref) (unref-func gsk_render_node_unref) (set-value-func gsk_value_set_render_node) (get-value-func gsk_value_get_render_node)
*
* The `GskRenderNode` structure contains only private data.
*
* Since: 3.90
*/
static void
value_render_node_init (GValue *value)
{
value->data[0].v_pointer = NULL;
}
static void
value_render_node_free (GValue *value)
{
if (value->data[0].v_pointer != NULL)
gsk_render_node_unref (value->data[0].v_pointer);
}
static void
value_render_node_copy (const GValue *src,
GValue *dst)
{
if (src->data[0].v_pointer != NULL)
dst->data[0].v_pointer = gsk_render_node_ref (src->data[0].v_pointer);
else
dst->data[0].v_pointer = NULL;
}
static gpointer
value_render_node_peek_pointer (const GValue *value)
{
return value->data[0].v_pointer;
}
static gchar *
value_render_node_collect_value (GValue *value,
guint n_collect_values,
GTypeCValue *collect_values,
guint collect_flags)
{
GskRenderNode *node;
node = collect_values[0].v_pointer;
if (node == NULL)
{
value->data[0].v_pointer = NULL;
return NULL;
}
if (node->parent_instance.g_class == NULL)
return g_strconcat ("invalid unclassed GskRenderNode pointer for "
"value type '",
G_VALUE_TYPE_NAME (value),
"'",
NULL);
value->data[0].v_pointer = gsk_render_node_ref (node);
return NULL;
}
static gchar *
value_render_node_lcopy_value (const GValue *value,
guint n_collect_values,
GTypeCValue *collect_values,
guint collect_flags)
{
GskRenderNode **node_p = collect_values[0].v_pointer;
if (node_p == NULL)
return g_strconcat ("value location for '",
G_VALUE_TYPE_NAME (value),
"' passed as NULL",
NULL);
if (value->data[0].v_pointer == NULL)
*node_p = NULL;
else if (collect_flags & G_VALUE_NOCOPY_CONTENTS)
*node_p = value->data[0].v_pointer;
else
*node_p = gsk_render_node_ref (value->data[0].v_pointer);
return NULL;
}
static void
gsk_render_node_finalize (GskRenderNode *self)
{
GskRenderNodeIter iter;
self->is_mutable = TRUE;
g_clear_pointer (&self->surface, cairo_surface_destroy);
g_clear_pointer (&self->texture, gsk_texture_unref);
g_clear_pointer (&self->name, g_free);
gsk_render_node_iter_init (&iter, self);
while (gsk_render_node_iter_next (&iter, NULL))
gsk_render_node_iter_remove (&iter);
g_type_free_instance ((GTypeInstance *) self);
}
static void
gsk_render_node_class_base_init (GskRenderNodeClass *klass)
{
}
static void
gsk_render_node_class_base_finalize (GskRenderNodeClass *klass)
{
}
static void
gsk_render_node_class_init (GskRenderNodeClass *klass)
{
klass->finalize = gsk_render_node_finalize;
}
static void
gsk_render_node_init (GskRenderNode *self)
{
self->ref_count = 1;
graphene_rect_init_from_rect (&self->bounds, graphene_rect_zero ());
graphene_matrix_init_identity (&self->transform);
graphene_point3d_init (&self->anchor_point, 0.f, 0.f, 0.f);
self->opacity = 1.0;
self->min_filter = GSK_SCALING_FILTER_NEAREST;
self->mag_filter = GSK_SCALING_FILTER_NEAREST;
self->is_mutable = TRUE;
self->needs_world_matrix_update = TRUE;
}
GType
gsk_render_node_get_type (void)
{
static volatile gsize gsk_render_node_type__volatile;
if (g_once_init_enter (&gsk_render_node_type__volatile))
{
static const GTypeFundamentalInfo finfo = {
(G_TYPE_FLAG_CLASSED |
G_TYPE_FLAG_INSTANTIATABLE |
G_TYPE_FLAG_DERIVABLE |
G_TYPE_FLAG_DEEP_DERIVABLE),
};
static const GTypeValueTable render_node_value_table = {
value_render_node_init,
value_render_node_free,
value_render_node_copy,
value_render_node_peek_pointer,
"p", value_render_node_collect_value,
"p", value_render_node_lcopy_value,
};
const GTypeInfo render_node_info = {
sizeof (GskRenderNodeClass),
(GBaseInitFunc) gsk_render_node_class_base_init,
(GBaseFinalizeFunc) gsk_render_node_class_base_finalize,
(GClassInitFunc) gsk_render_node_class_init,
(GClassFinalizeFunc) NULL,
NULL,
sizeof (GskRenderNode), 16,
(GInstanceInitFunc) gsk_render_node_init,
&render_node_value_table,
};
GType gsk_render_node_type =
g_type_register_fundamental (g_type_fundamental_next (),
g_intern_static_string ("GskRenderNode"),
&render_node_info,
&finfo,
0);
g_once_init_leave (&gsk_render_node_type__volatile, gsk_render_node_type);
}
return gsk_render_node_type__volatile;
}
/*< private >
* gsk_render_node_new:
* @renderer: a #GskRenderer
*
* Returns: (transfer full): the newly created #GskRenderNode
*/
GskRenderNode *
gsk_render_node_new (void)
{
GskRenderNode *res = (GskRenderNode *) g_type_create_instance (GSK_TYPE_RENDER_NODE);
return res;
}
/**
* gsk_render_node_ref:
* @node: a #GskRenderNode
*
* Acquires a reference on the given #GskRenderNode.
*
* Returns: (transfer none): the #GskRenderNode with an additional reference
*
* Since: 3.90
*/
GskRenderNode *
gsk_render_node_ref (GskRenderNode *node)
{
g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL);
g_atomic_int_inc (&node->ref_count);
return node;
}
/**
* gsk_render_node_unref:
* @node: a #GskRenderNode
*
* Releases a reference on the given #GskRenderNode.
*
* If the reference was the last, the resources associated to the @node are
* freed.
*
* Since: 3.90
*/
void
gsk_render_node_unref (GskRenderNode *node)
{
g_return_if_fail (GSK_IS_RENDER_NODE (node));
if (g_atomic_int_dec_and_test (&node->ref_count))
GSK_RENDER_NODE_GET_CLASS (node)->finalize (node);
}
/**
* gsk_render_node_get_parent:
* @node: a #GskRenderNode
*
* Returns the parent of the @node.
*
* Returns: (transfer none): the parent of the #GskRenderNode
*
* Since: 3.90
*/
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.90
*/
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:
* @node: a #GskRenderNode
*
* Returns the last child of @node.
*
* Returns: (transfer none): the last child of the #GskRenderNode
*
* Since: 3.90
*/
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:
* @node: a #GskRenderNode
*
* Returns the next sibling of @node.
*
* Returns: (transfer none): the next sibling of the #GskRenderNode
*
* Since: 3.90
*/
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:
* @node: a #GskRenderNode
*
* Returns the previous sibling of @node.
*
* Returns: (transfer none): the previous sibling of the #GskRenderNode
*
* Since: 3.90
*/
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;
}
if (!node->is_mutable)
{
g_critical ("The render node of type '%s' is immutable.",
G_OBJECT_TYPE_NAME (node));
return;
}
insert_func (node, child, insert_func_data);
gsk_render_node_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;
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_append_child:
* @node: a #GskRenderNode
* @child: a #GskRenderNode
*
* Appends @child to the list of children of @node.
*
* This function acquires a reference on @child.
*
* Returns: (transfer none): the #GskRenderNode
*
* Since: 3.90
*/
GskRenderNode *
gsk_render_node_append_child (GskRenderNode *node,
GskRenderNode *child)
{
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 (node->is_mutable, node);
gsk_render_node_insert_child_internal (node, child,
insert_child_at_pos,
GINT_TO_POINTER (node->n_children));
return node;
}
/**
* gsk_render_node_prepend_child:
* @node: a #GskRenderNode
* @child: a #GskRenderNode
*
* Prepends @child to the list of children of @node.
*
* This function acquires a reference on @child.
*
* Returns: (transfer none): the #GskRenderNode
*
* Since: 3.90
*/
GskRenderNode *
gsk_render_node_prepend_child (GskRenderNode *node,
GskRenderNode *child)
{
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 (node->is_mutable, node);
gsk_render_node_insert_child_internal (node, child,
insert_child_at_pos,
GINT_TO_POINTER (0));
return node;
}
/**
* 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.90
*/
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);
g_return_val_if_fail (node->is_mutable, 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.90
*/
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);
g_return_val_if_fail (node->is_mutable, 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.90
*/
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);
g_return_val_if_fail (node->is_mutable, 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.90
*/
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);
g_return_val_if_fail (node->is_mutable, 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);
g_return_val_if_fail (node->is_mutable, 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;
gsk_render_node_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.90
*/
GskRenderNode *
gsk_render_node_remove_all_children (GskRenderNode *node)
{
GskRenderNodeIter iter;
g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL);
g_return_val_if_fail (node->is_mutable, node);
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.90
*/
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.90
*/
void
gsk_render_node_set_bounds (GskRenderNode *node,
const graphene_rect_t *bounds)
{
g_return_if_fail (GSK_IS_RENDER_NODE (node));
g_return_if_fail (node->is_mutable);
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_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.90
*/
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.90
*/
void
gsk_render_node_set_transform (GskRenderNode *node,
const graphene_matrix_t *transform)
{
g_return_if_fail (GSK_IS_RENDER_NODE (node));
g_return_if_fail (node->is_mutable);
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_get_transform:
* @node: a #GskRenderNode
* @mv: (out caller-allocates): return location for the transform matrix
*
* Retrieves the transform matrix set using gsk_render_node_set_transform().
*
* Since: 3.90
*/
void
gsk_render_node_get_transform (GskRenderNode *node,
graphene_matrix_t *mv)
{
g_return_if_fail (GSK_IS_RENDER_NODE (node));
g_return_if_fail (mv != NULL);
graphene_matrix_init_from_matrix (mv, &node->transform);
}
/**
* gsk_render_node_set_anchor_point:
* @node: a #GskRenderNode
* @offset: the anchor point
*
* Set the anchor point used when rendering the @node.
*
* Since: 3.90
*/
void
gsk_render_node_set_anchor_point (GskRenderNode *node,
const graphene_point3d_t *offset)
{
g_return_if_fail (GSK_IS_RENDER_NODE (node));
g_return_if_fail (node->is_mutable);
graphene_point3d_init_from_point (&node->anchor_point, offset);
}
/**
* 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.90
*/
void
gsk_render_node_set_opacity (GskRenderNode *node,
double opacity)
{
g_return_if_fail (GSK_IS_RENDER_NODE (node));
g_return_if_fail (node->is_mutable);
node->opacity = CLAMP (opacity, 0.0, 1.0);
}
/**
* 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.90
*/
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.90
*/
void
gsk_render_node_set_hidden (GskRenderNode *node,
gboolean hidden)
{
g_return_if_fail (GSK_IS_RENDER_NODE (node));
g_return_if_fail (node->is_mutable);
node->hidden = !!hidden;
}
/**
* gsk_render_node_is_hidden:
* @node: a #GskRenderNode
*
* Checks whether a @node is hidden.
*
* Returns: %TRUE if the #GskRenderNode is hidden
*
* Since: 3.90
*/
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.90
*/
void
gsk_render_node_set_opaque (GskRenderNode *node,
gboolean opaque)
{
g_return_if_fail (GSK_IS_RENDER_NODE (node));
g_return_if_fail (node->is_mutable);
node->opaque = !!opaque;
}
/**
* 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.90
*/
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.90
*/
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;
}
/*< 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]\n",
node,
node->parent != NULL ? node->parent : 0,
node->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;
graphene_matrix_init_identity (&tmp);
if (node->transform_set)
graphene_matrix_multiply (&tmp, &node->transform, &tmp);
graphene_matrix_translate (&tmp, &node->anchor_point);
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);
}
gboolean
gsk_render_node_has_surface (GskRenderNode *node)
{
g_return_val_if_fail (GSK_IS_RENDER_NODE (node), FALSE);
return node->surface != NULL;
}
gboolean
gsk_render_node_has_texture (GskRenderNode *node)
{
g_return_val_if_fail (GSK_IS_RENDER_NODE (node), FALSE);
return node->texture != NULL;
}
GskTexture *
gsk_render_node_get_texture (GskRenderNode *node)
{
g_return_val_if_fail (GSK_IS_RENDER_NODE (node), 0);
return node->texture;
}
/**
* gsk_render_node_set_texture:
* @node: a #GskRenderNode
* @texture: the #GskTexture
*
* Associates a #GskTexture to a #GskRenderNode.
*
* Since: 3.90
*/
void
gsk_render_node_set_texture (GskRenderNode *node,
GskTexture *texture)
{
g_return_if_fail (GSK_IS_RENDER_NODE (node));
if (node->texture == texture)
return;
if (node->texture)
gsk_texture_unref (node->texture);
node->texture = texture;
if (texture)
gsk_texture_ref (texture);
}
/*< 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;
}
void
gsk_render_node_set_scaling_filters (GskRenderNode *node,
GskScalingFilter min_filter,
GskScalingFilter mag_filter)
{
g_return_if_fail (GSK_IS_RENDER_NODE (node));
if (node->min_filter != min_filter)
node->min_filter = min_filter;
if (node->mag_filter != mag_filter)
node->mag_filter = mag_filter;
}
/*< 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;
}
/**
* 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.90
*/
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);
}
/**
* gsk_render_node_get_name:
* @node: a #GskRenderNode
*
* Retrieves the name previously set via gsk_render_node_set_name().
* If no name has been set, %NULL is returned.
*
* Returns: (nullable): The name previously set via
* gsk_render_node_set_name() or %NULL
*
* Since: 3.90
**/
const char *
gsk_render_node_get_name (GskRenderNode *node)
{
g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL);
return node->name;
}
/**
* gsk_render_node_set_blend_mode:
* @node: a #GskRenderNode
* @blend_mode: the blend mode to be applied to the node's children
*
* Sets the blend mode to be used when rendering the children
* of the @node.
*
* The default value is %GSK_BLEND_MODE_DEFAULT.
*
* Since: 3.90
*/
void
gsk_render_node_set_blend_mode (GskRenderNode *node,
GskBlendMode blend_mode)
{
g_return_if_fail (GSK_IS_RENDER_NODE (node));
g_return_if_fail (node->is_mutable);
if (node->blend_mode == blend_mode)
return;
node->blend_mode = blend_mode;
}
/*
* gsk_render_node_get_blend_mode:
* @node: a #GskRenderNode
*
* Retrieves the blend mode set by gsk_render_node_set_blend_mode().
*
* Returns: the blend mode
*/
GskBlendMode
gsk_render_node_get_blend_mode (GskRenderNode *node)
{
g_return_val_if_fail (GSK_IS_RENDER_NODE (node), GSK_BLEND_MODE_DEFAULT);
return node->blend_mode;
}
/**
* gsk_render_node_get_draw_context:
* @node: a #GskRenderNode
* @renderer: (nullable): Renderer to optimize for or %NULL for any
*
* Creates a Cairo context for drawing using the surface associated
* to the render node.
* If no surface exists yet, a surface will be created optimized for
* rendering to @renderer.
*
* Returns: (transfer full): a Cairo context used for drawing; use
* cairo_destroy() when done drawing
*
* Since: 3.90
*/
cairo_t *
gsk_render_node_get_draw_context (GskRenderNode *node,
GskRenderer *renderer)
{
int width, height;
cairo_t *res;
g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL);
g_return_val_if_fail (node->is_mutable, NULL);
g_return_val_if_fail (renderer == NULL || GSK_IS_RENDERER (renderer), NULL);
width = ceilf (node->bounds.size.width);
height = ceilf (node->bounds.size.height);
if (width <= 0 || height <= 0)
{
cairo_surface_t *surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 0, 0);
res = cairo_create (surface);
cairo_surface_destroy (surface);
}
else if (node->surface == NULL)
{
if (renderer)
{
node->surface = gsk_renderer_create_cairo_surface (renderer,
node->opaque ? CAIRO_FORMAT_RGB24
: CAIRO_FORMAT_ARGB32,
ceilf (node->bounds.size.width),
ceilf (node->bounds.size.height));
}
else
{
node->surface = cairo_image_surface_create (node->opaque ? CAIRO_FORMAT_RGB24
: CAIRO_FORMAT_ARGB32,
ceilf (node->bounds.size.width),
ceilf (node->bounds.size.height));
}
res = cairo_create (node->surface);
}
else
{
res = cairo_create (node->surface);
}
cairo_translate (res, -node->bounds.origin.x, -node->bounds.origin.y);
cairo_rectangle (res,
node->bounds.origin.x, node->bounds.origin.y,
node->bounds.size.width, node->bounds.size.height);
cairo_clip (res);
if (GSK_DEBUG_CHECK (SURFACE))
{
const char *prefix;
prefix = g_getenv ("GSK_DEBUG_PREFIX");
if (!prefix || g_str_has_prefix (node->name, prefix))
{
cairo_save (res);
cairo_rectangle (res,
node->bounds.origin.x + 1, node->bounds.origin.y + 1,
node->bounds.size.width - 2, node->bounds.size.height - 2);
cairo_set_line_width (res, 2);
cairo_set_source_rgb (res, 1, 0, 0);
cairo_stroke (res);
cairo_restore (res);
}
}
return res;
}
/*< private >
* gsk_render_node_make_immutable:
* @node: a #GskRenderNode
*
* Marks @node, and all its children, as immutable.
*/
void
gsk_render_node_make_immutable (GskRenderNode *node)
{
GskRenderNodeIter iter;
GskRenderNode *child;
if (!node->is_mutable)
return;
node->is_mutable = FALSE;
gsk_render_node_iter_init (&iter, node);
while (gsk_render_node_iter_next (&iter, &child))
gsk_render_node_make_immutable (child);
}
/*< private >
* gsk_render_node_get_size:
* @root: a #GskRenderNode
*
* Computes the total number of children of @root.
*
* Returns: the size of the tree
*/
int
gsk_render_node_get_size (GskRenderNode *root)
{
GskRenderNodeIter iter;
GskRenderNode *child;
int res;
g_return_val_if_fail (GSK_IS_RENDER_NODE (root), 0);
res = 1;
gsk_render_node_iter_init (&iter, root);
while (gsk_render_node_iter_next (&iter, &child))
res += gsk_render_node_get_size (child);
return res;
}
/**
* gsk_value_set_render_node:
* @value: a #GValue
* @node: (nullable): a #GskRenderNode
*
* Sets the @node into the @value.
*
* This function acquires a reference on @node.
*
* Since: 3.90
*/
void
gsk_value_set_render_node (GValue *value,
GskRenderNode *node)
{
GskRenderNode *old_node;
g_return_if_fail (GSK_VALUE_HOLDS_RENDER_NODE (value));
old_node = value->data[0].v_pointer;
if (node != NULL)
{
g_return_if_fail (GSK_IS_RENDER_NODE (node));
value->data[0].v_pointer = gsk_render_node_ref (node);
}
else
value->data[0].v_pointer = NULL;
if (old_node != NULL)
gsk_render_node_unref (old_node);
}
/**
* gsk_value_take_render_node:
* @value: a #GValue
* @node: (transfer full) (nullable): a #GskRenderNode
*
* Sets the @node into the @value, without taking a reference to it.
*
* Since: 3.90
*/
void
gsk_value_take_render_node (GValue *value,
GskRenderNode *node)
{
GskRenderNode *old_node;
g_return_if_fail (GSK_VALUE_HOLDS_RENDER_NODE (value));
old_node = value->data[0].v_pointer;
if (node != NULL)
{
g_return_if_fail (GSK_IS_RENDER_NODE (node));
/* take over ownership */
value->data[0].v_pointer = node;
}
else
value->data[0].v_pointer = NULL;
if (old_node != NULL)
gsk_render_node_unref (old_node);
}
/**
* gsk_value_get_render_node:
* @value: a #GValue
*
* Retrieves the #GskRenderNode stored inside the @value.
*
* Returns: (transfer none) (nullable): a #GskRenderNode
*
* Since: 3.90
*/
GskRenderNode *
gsk_value_get_render_node (const GValue *value)
{
g_return_val_if_fail (GSK_VALUE_HOLDS_RENDER_NODE (value), NULL);
return value->data[0].v_pointer;
}
/**
* gsk_value_dup_render_node:
* @value: a #GValue
*
* Retrieves the #GskRenderNode stored inside the @value, and
* acquires a reference to it.
*
* Returns: (transfer none) (nullable): a #GskRenderNode
*
* Since: 3.90
*/
GskRenderNode *
gsk_value_dup_render_node (const GValue *value)
{
g_return_val_if_fail (GSK_VALUE_HOLDS_RENDER_NODE (value), NULL);
if (value->data[0].v_pointer != NULL)
return gsk_render_node_ref (value->data[0].v_pointer);
return NULL;
}