forked from AuroraMiddleware/gtk
7afdd3fdb5
GSK is conceptually split into two scene graphs: * a simple rendering tree of operations * a complex set of logical layers The latter is built on the former, and adds convenience and high level API for application developers. The lower layer, though, is what gets transformed into the rendering pipeline, as it's simple and thus can be transformed into appropriate rendering commands with minimal state changes. The lower layer is also suitable for reuse from more complex higher layers, like the CSS machinery in GTK, without necessarily port those layers to the GSK high level API. This lower layer is based on GskRenderNode instances, which represent the tree of rendering operations; and a GskRenderer instance, which takes the render nodes and submits them (after potentially reordering and transforming them to a more appropriate representation) to the underlying graphic system.
1247 lines
32 KiB
C
1247 lines
32 KiB
C
/* 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;
|
|
}
|