mutter/clutter/clutter-group.c
Robert Bragg 3540d222e1 paint volumes: another pass at the design
This is a fairly extensive second pass at exposing paint volumes for
actors.

The API has changed to allow clutter_actor_get_paint_volume to fail
since there are times - such as when an actor isn't a descendent of the
stage - when the volume can't be determined. Another example is when
something has connected to the "paint" signal of the actor and we simply
have no way of knowing what might be drawn in that handler.

The API has also be changed to return a const ClutterPaintVolume pointer
(transfer none) so we can avoid having to dynamically allocate the
volumes in the most common/performance critical code paths. Profiling was
showing the slice allocation of volumes taking about 1% of an apps time,
for some fairly basic tests. Most volumes can now simply be allocated on
the stack; for clutter_actor_get_paint_volume we return a pointer to
&priv->paint_volume and if we need a more dynamic allocation there is
now a _clutter_stage_paint_volume_stack_allocate() mechanism which lets
us allocate data which expires at the start of the next frame.

The API has been extended to make it easier to implement
get_paint_volume for containers by using
clutter_actor_get_transformed_paint_volume and
clutter_paint_volume_union. The first allows you to query the paint
volume of a child but transformed into parent actor coordinates. The
second lets you combine volumes together so you can union all the
volumes for a container's children and report that as the container's
own volume.

The representation of paint volumes has been updated to consider that
2D actors are the most common.

The effect apis, clutter-texture and clutter-group have been update
accordingly.
2010-09-29 15:12:57 +01:00

523 lines
15 KiB
C

/*
* Clutter.
*
* An OpenGL based 'interactive canvas' library.
*
* Authored By Matthew Allum <mallum@openedhand.com>
*
* Copyright (C) 2006 OpenedHand
*
* 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:clutter-group
* @short_description: A fixed layout container
*
* A #ClutterGroup is an Actor which contains multiple child actors positioned
* relative to the #ClutterGroup position. Other operations such as scaling,
* rotating and clipping of the group will apply to the child actors.
*
* A #ClutterGroup's size is defined by the size and position of its children;
* it will be the smallest non-negative size that covers the right and bottom
* edges of all of its children.
*
* Setting the size on a Group using #ClutterActor methods like
* clutter_actor_set_size() will override the natural size of the Group,
* however this will not affect the size of the children and they may still
* be painted outside of the allocation of the group. One way to constrain
* the visible area of a #ClutterGroup to a specified allocation is to
* explicitly set the size of the #ClutterGroup and then use the
* #ClutterActor:clip-to-allocation property.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdarg.h>
#include "clutter-group.h"
#include "clutter-container.h"
#include "clutter-fixed-layout.h"
#include "clutter-main.h"
#include "clutter-debug.h"
#include "clutter-enum-types.h"
#include "clutter-marshal.h"
#include "clutter-private.h"
#include "cogl/cogl.h"
#define CLUTTER_GROUP_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLUTTER_TYPE_GROUP, ClutterGroupPrivate))
struct _ClutterGroupPrivate
{
GList *children;
ClutterLayoutManager *layout;
};
enum
{
ADD,
REMOVE,
LAST_SIGNAL
};
static void clutter_container_iface_init (ClutterContainerIface *iface);
G_DEFINE_TYPE_WITH_CODE (ClutterGroup, clutter_group, CLUTTER_TYPE_ACTOR,
G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTAINER,
clutter_container_iface_init));
static gint
sort_by_depth (gconstpointer a,
gconstpointer b)
{
gfloat depth_a = clutter_actor_get_depth (CLUTTER_ACTOR(a));
gfloat depth_b = clutter_actor_get_depth (CLUTTER_ACTOR(b));
if (depth_a < depth_b)
return -1;
if (depth_a > depth_b)
return 1;
return 0;
}
static void
clutter_group_real_add (ClutterContainer *container,
ClutterActor *actor)
{
ClutterGroupPrivate *priv = CLUTTER_GROUP (container)->priv;
g_object_ref (actor);
priv->children = g_list_append (priv->children, actor);
clutter_actor_set_parent (actor, CLUTTER_ACTOR (container));
/* queue a relayout, to get the correct positioning inside
* the ::actor-added signal handlers
*/
clutter_actor_queue_relayout (CLUTTER_ACTOR (container));
g_signal_emit_by_name (container, "actor-added", actor);
clutter_container_sort_depth_order (container);
g_object_unref (actor);
}
static void
clutter_group_real_remove (ClutterContainer *container,
ClutterActor *actor)
{
ClutterGroupPrivate *priv = CLUTTER_GROUP (container)->priv;
g_object_ref (actor);
priv->children = g_list_remove (priv->children, actor);
clutter_actor_unparent (actor);
/* queue a relayout, to get the correct positioning inside
* the ::actor-removed signal handlers
*/
clutter_actor_queue_relayout (CLUTTER_ACTOR (container));
/* at this point, the actor passed to the "actor-removed" signal
* handlers is not parented anymore to the container but since we
* are holding a reference on it, it's still valid
*/
g_signal_emit_by_name (container, "actor-removed", actor);
clutter_actor_queue_redraw (CLUTTER_ACTOR (container));
g_object_unref (actor);
}
static void
clutter_group_real_foreach (ClutterContainer *container,
ClutterCallback callback,
gpointer user_data)
{
ClutterGroupPrivate *priv = CLUTTER_GROUP (container)->priv;
/* Using g_list_foreach instead of iterating the list manually
because it has better protection against the current node being
removed. This will happen for example if someone calls
clutter_container_foreach(container, clutter_actor_destroy) */
g_list_foreach (priv->children, (GFunc) callback, user_data);
}
static void
clutter_group_real_raise (ClutterContainer *container,
ClutterActor *actor,
ClutterActor *sibling)
{
ClutterGroupPrivate *priv = CLUTTER_GROUP (container)->priv;
priv->children = g_list_remove (priv->children, actor);
/* Raise at the top */
if (!sibling)
{
GList *last_item;
last_item = g_list_last (priv->children);
if (last_item)
sibling = last_item->data;
priv->children = g_list_append (priv->children, actor);
}
else
{
gint index_ = g_list_index (priv->children, sibling) + 1;
priv->children = g_list_insert (priv->children, actor, index_);
}
/* set Z ordering a value below, this will then call sort
* as values are equal ordering shouldn't change but Z
* values will be correct.
*
* FIXME: optimise
*/
if (sibling &&
clutter_actor_get_depth (sibling) != clutter_actor_get_depth (actor))
{
clutter_actor_set_depth (actor, clutter_actor_get_depth (sibling));
}
clutter_actor_queue_redraw (CLUTTER_ACTOR (container));
}
static void
clutter_group_real_lower (ClutterContainer *container,
ClutterActor *actor,
ClutterActor *sibling)
{
ClutterGroup *self = CLUTTER_GROUP (container);
ClutterGroupPrivate *priv = self->priv;
priv->children = g_list_remove (priv->children, actor);
/* Push to bottom */
if (!sibling)
{
GList *last_item;
last_item = g_list_first (priv->children);
if (last_item)
sibling = last_item->data;
priv->children = g_list_prepend (priv->children, actor);
}
else
{
gint index_ = g_list_index (priv->children, sibling);
priv->children = g_list_insert (priv->children, actor, index_);
}
/* See comment in group_raise for this */
if (sibling &&
clutter_actor_get_depth (sibling) != clutter_actor_get_depth (actor))
{
clutter_actor_set_depth (actor, clutter_actor_get_depth (sibling));
}
clutter_actor_queue_redraw (CLUTTER_ACTOR (container));
}
static void
clutter_group_real_sort_depth_order (ClutterContainer *container)
{
ClutterGroupPrivate *priv = CLUTTER_GROUP (container)->priv;
priv->children = g_list_sort (priv->children, sort_by_depth);
clutter_actor_queue_redraw (CLUTTER_ACTOR (container));
}
static void
clutter_container_iface_init (ClutterContainerIface *iface)
{
iface->add = clutter_group_real_add;
iface->remove = clutter_group_real_remove;
iface->foreach = clutter_group_real_foreach;
iface->raise = clutter_group_real_raise;
iface->lower = clutter_group_real_lower;
iface->sort_depth_order = clutter_group_real_sort_depth_order;
}
static void
clutter_group_real_paint (ClutterActor *actor)
{
ClutterGroupPrivate *priv = CLUTTER_GROUP (actor)->priv;
CLUTTER_NOTE (PAINT, "ClutterGroup paint enter '%s'",
clutter_actor_get_name (actor) ? clutter_actor_get_name (actor)
: "unknown");
g_list_foreach (priv->children, (GFunc) clutter_actor_paint, NULL);
CLUTTER_NOTE (PAINT, "ClutterGroup paint leave '%s'",
clutter_actor_get_name (actor) ? clutter_actor_get_name (actor)
: "unknown");
}
static void
clutter_group_real_pick (ClutterActor *actor,
const ClutterColor *pick)
{
ClutterGroupPrivate *priv = CLUTTER_GROUP (actor)->priv;
/* Chain up so we get a bounding box pained (if we are reactive) */
CLUTTER_ACTOR_CLASS (clutter_group_parent_class)->pick (actor, pick);
g_list_foreach (priv->children, (GFunc) clutter_actor_paint, NULL);
}
static void
clutter_group_real_get_preferred_width (ClutterActor *actor,
gfloat for_height,
gfloat *min_width,
gfloat *natural_width)
{
ClutterGroupPrivate *priv = CLUTTER_GROUP (actor)->priv;
clutter_layout_manager_get_preferred_width (priv->layout,
CLUTTER_CONTAINER (actor),
for_height,
min_width, natural_width);
}
static void
clutter_group_real_get_preferred_height (ClutterActor *actor,
gfloat for_width,
gfloat *min_height,
gfloat *natural_height)
{
ClutterGroupPrivate *priv = CLUTTER_GROUP (actor)->priv;
clutter_layout_manager_get_preferred_height (priv->layout,
CLUTTER_CONTAINER (actor),
for_width,
min_height, natural_height);
}
static void
clutter_group_real_allocate (ClutterActor *actor,
const ClutterActorBox *allocation,
ClutterAllocationFlags flags)
{
ClutterGroupPrivate *priv = CLUTTER_GROUP (actor)->priv;
ClutterActorClass *klass;
klass = CLUTTER_ACTOR_CLASS (clutter_group_parent_class);
klass->allocate (actor, allocation, flags);
if (priv->children == NULL)
return;
clutter_layout_manager_allocate (priv->layout,
CLUTTER_CONTAINER (actor),
allocation, flags);
}
static void
clutter_group_dispose (GObject *object)
{
ClutterGroup *self = CLUTTER_GROUP (object);
ClutterGroupPrivate *priv = self->priv;
if (priv->children)
{
g_list_foreach (priv->children, (GFunc) clutter_actor_destroy, NULL);
g_list_free (priv->children);
priv->children = NULL;
}
if (priv->layout)
{
clutter_layout_manager_set_container (priv->layout, NULL);
g_object_unref (priv->layout);
priv->layout = NULL;
}
G_OBJECT_CLASS (clutter_group_parent_class)->dispose (object);
}
static void
clutter_group_real_show_all (ClutterActor *actor)
{
clutter_container_foreach (CLUTTER_CONTAINER (actor),
CLUTTER_CALLBACK (clutter_actor_show),
NULL);
clutter_actor_show (actor);
}
static void
clutter_group_real_hide_all (ClutterActor *actor)
{
clutter_actor_hide (actor);
clutter_container_foreach (CLUTTER_CONTAINER (actor),
CLUTTER_CALLBACK (clutter_actor_hide),
NULL);
}
static gboolean
clutter_group_real_get_paint_volume (ClutterActor *actor,
ClutterPaintVolume *volume)
{
ClutterGroupPrivate *priv = CLUTTER_GROUP (actor)->priv;
GList *l;
if (priv->children == NULL)
return TRUE;
for (l = priv->children; l != NULL; l = l->next)
{
ClutterActor *child = l->data;
const ClutterPaintVolume *child_volume;
/* This gets the paint volume of the child transformed into the
* group's coordinate space... */
child_volume = clutter_actor_get_transformed_paint_volume (child, actor);
if (!child_volume)
return FALSE;
clutter_paint_volume_union (volume, child_volume);
}
return TRUE;
}
static void
clutter_group_class_init (ClutterGroupClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
g_type_class_add_private (klass, sizeof (ClutterGroupPrivate));
actor_class->get_preferred_width = clutter_group_real_get_preferred_width;
actor_class->get_preferred_height = clutter_group_real_get_preferred_height;
actor_class->allocate = clutter_group_real_allocate;
actor_class->paint = clutter_group_real_paint;
actor_class->pick = clutter_group_real_pick;
actor_class->show_all = clutter_group_real_show_all;
actor_class->hide_all = clutter_group_real_hide_all;
actor_class->get_paint_volume = clutter_group_real_get_paint_volume;
gobject_class->dispose = clutter_group_dispose;
}
static void
clutter_group_init (ClutterGroup *self)
{
self->priv = CLUTTER_GROUP_GET_PRIVATE (self);
self->priv->layout = clutter_fixed_layout_new ();
g_object_ref_sink (self->priv->layout);
clutter_layout_manager_set_container (self->priv->layout,
CLUTTER_CONTAINER (self));
}
/**
* clutter_group_new:
*
* Create a new #ClutterGroup.
*
* Return value: the newly created #ClutterGroup actor
*/
ClutterActor *
clutter_group_new (void)
{
return g_object_new (CLUTTER_TYPE_GROUP, NULL);
}
/**
* clutter_group_remove_all:
* @group: A #ClutterGroup
*
* Removes all children actors from the #ClutterGroup.
*/
void
clutter_group_remove_all (ClutterGroup *group)
{
GList *children;
g_return_if_fail (CLUTTER_IS_GROUP (group));
children = group->priv->children;
while (children)
{
ClutterActor *child = children->data;
children = children->next;
clutter_container_remove_actor (CLUTTER_CONTAINER (group), child);
}
}
/**
* clutter_group_get_n_children:
* @self: A #ClutterGroup
*
* Gets the number of actors held in the group.
*
* Return value: The number of child actors held in the group.
*
* Since: 0.2
*/
gint
clutter_group_get_n_children (ClutterGroup *self)
{
g_return_val_if_fail (CLUTTER_IS_GROUP (self), 0);
return g_list_length (self->priv->children);
}
/**
* clutter_group_get_nth_child:
* @self: A #ClutterGroup
* @index_: the position of the requested actor.
*
* Gets a groups child held at @index_ in stack.
*
* Return value: (transfer none): A Clutter actor, or %NULL if
* @index_ is invalid.
*
* Since: 0.2
*/
ClutterActor *
clutter_group_get_nth_child (ClutterGroup *self,
gint index_)
{
g_return_val_if_fail (CLUTTER_IS_GROUP (self), NULL);
return g_list_nth_data (self->priv->children, index_);
}