mutter/clutter/clutter-group.c
Emmanuele Bassi d6d208da7d Remove Units from the public API
With the recent change to internal floating point values, ClutterUnit
has become a redundant type, defined to be a float. All integer entry
points are being internally converted to floating point values to be
passed to the GL pipeline with the least amount of conversion.

ClutterUnit is thus exposed as just a "pixel with fractionary bits",
and not -- as users might think -- as generic, resolution and device
independent units. not that it was the case, but a definitive amount
of people was convinced it did provide this "feature", and was flummoxed
about the mere existence of this type.

So, having ClutterUnit exposed in the public API doubles the entry
points and has the following disadvantages:

  - we have to maintain twice the amount of entry points in ClutterActor
  - we still do an integer-to-float implicit conversion
  - we introduce a weird impedance between pixels and "pixels with
    fractionary bits"
  - language bindings will have to choose what to bind, and resort
    to manually overriding the API
    + *except* for language bindings based on GObject-Introspection, as
      they cannot do manual overrides, thus will replicate the entire
      set of entry points

For these reason, we should coalesces every Actor entry point for
pixels and for ClutterUnit into a single entry point taking a float,
like:

  void clutter_actor_set_x (ClutterActor *self,
                            gfloat        x);
  void clutter_actor_get_size (ClutterActor *self,
                               gfloat       *width,
                               gfloat       *height);
  gfloat clutter_actor_get_height (ClutterActor *self);

etc.

The issues I have identified are:

  - we'll have a two cases of compiler warnings:
    - printf() format of the return values from %d to %f
    - clutter_actor_get_size() taking floats instead of unsigned ints
  - we'll have a problem with varargs when passing an integer instead
    of a floating point value, except on 64bit platforms where the
    size of a float is the same as the size of an int

To be clear: the *intent* of the API should not change -- we still use
pixels everywhere -- but:

  - we remove ambiguity in the API with regard to pixels and units
  - we remove entry points we get to maintain for the whole 1.0
    version of the API
  - we make things simpler to bind for both manual language bindings
    and automatic (gobject-introspection based) ones
  - we have the simplest API possible while still exposing the
    capabilities of the underlying GL implementation
2009-05-06 16:44:47 +01:00

660 lines
18 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, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/**
* SECTION:clutter-group
* @short_description: Actor class containing multiple children.
* actors.
*
* 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.
* Resize requests via the #ClutterActor API will be ignored.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdarg.h>
#include "clutter-group.h"
#include "clutter-container.h"
#include "clutter-main.h"
#include "clutter-private.h"
#include "clutter-debug.h"
#include "clutter-marshal.h"
#include "clutter-enum-types.h"
#include "cogl/cogl.h"
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));
#define CLUTTER_GROUP_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLUTTER_TYPE_GROUP, ClutterGroupPrivate))
struct _ClutterGroupPrivate
{
GList *children;
};
static void
clutter_group_paint (ClutterActor *actor)
{
ClutterGroupPrivate *priv = CLUTTER_GROUP (actor)->priv;
GList *child_item;
CLUTTER_NOTE (PAINT, "ClutterGroup paint enter '%s'",
clutter_actor_get_name (actor) ? clutter_actor_get_name (actor)
: "unknown");
for (child_item = priv->children;
child_item != NULL;
child_item = child_item->next)
{
ClutterActor *child = child_item->data;
g_assert (child != NULL);
if (CLUTTER_ACTOR_IS_VISIBLE (child))
clutter_actor_paint (child);
}
CLUTTER_NOTE (PAINT, "ClutterGroup paint leave '%s'",
clutter_actor_get_name (actor) ? clutter_actor_get_name (actor)
: "unknown");
}
static void
clutter_group_pick (ClutterActor *actor,
const ClutterColor *color)
{
/* Chain up so we get a bounding box pained (if we are reactive) */
CLUTTER_ACTOR_CLASS (clutter_group_parent_class)->pick (actor, color);
/* Just forward to the paint call which in turn will trigger
* the child actors also getting 'picked'.
*/
if (CLUTTER_ACTOR_IS_VISIBLE (actor))
clutter_group_paint (actor);
}
static void
clutter_fixed_layout_get_preferred_width (GList *children,
gfloat *min_width_p,
gfloat *natural_width_p)
{
GList *l;
gfloat min_left, min_right;
gfloat natural_left, natural_right;
min_left = 0;
min_right = 0;
natural_left = 0;
natural_right = 0;
for (l = children; l != NULL; l = l->next)
{
ClutterActor *child = l->data;
gfloat child_x, child_min, child_natural;
child_x = clutter_actor_get_x (child);
clutter_actor_get_preferred_size (child,
&child_min, NULL,
&child_natural, NULL);
if (l == children)
{
/* First child */
min_left = child_x;
natural_left = child_x;
min_right = min_left + child_min;
natural_right = natural_left + child_natural;
}
else
{
/* Union of extents with previous children */
if (child_x < min_left)
min_left = child_x;
if (child_x < natural_left)
natural_left = child_x;
if (child_x + child_min > min_right)
min_right = child_x + child_min;
if (child_x + child_natural > natural_right)
natural_right = child_x + child_natural;
}
}
/* The preferred size is defined as the width and height we want starting
* from our origin, since our allocation will set the origin; so we now
* need to remove any part of the request that is to the left of the origin.
*/
if (min_left < 0)
min_left = 0;
if (natural_left < 0)
natural_left = 0;
if (min_right < 0)
min_right = 0;
if (natural_right < 0)
natural_right = 0;
g_assert (min_right >= min_left);
g_assert (natural_right >= natural_left);
if (min_width_p)
*min_width_p = min_right - min_left;
if (natural_width_p)
*natural_width_p = natural_right - min_left;
}
static void
clutter_fixed_layout_get_preferred_height (GList *children,
gfloat *min_height_p,
gfloat *natural_height_p)
{
GList *l;
gfloat min_top, min_bottom;
gfloat natural_top, natural_bottom;
min_top = 0;
min_bottom = 0;
natural_top = 0;
natural_bottom = 0;
for (l = children; l != NULL; l = l->next)
{
ClutterActor *child = l->data;
gfloat child_y, child_min, child_natural;
child_y = clutter_actor_get_y (child);
clutter_actor_get_preferred_size (child,
NULL, &child_min,
NULL, &child_natural);
if (l == children)
{
/* First child */
min_top = child_y;
natural_top = child_y;
min_bottom = min_top + child_min;
natural_bottom = natural_top + child_natural;
}
else
{
/* Union of extents with previous children */
if (child_y < min_top)
min_top = child_y;
if (child_y < natural_top)
natural_top = child_y;
if (child_y + child_min > min_bottom)
min_bottom = child_y + child_min;
if (child_y + child_natural > natural_bottom)
natural_bottom = child_y + child_natural;
}
}
/* The preferred size is defined as the width and height we want starting
* from our origin, since our allocation will set the origin; so we now
* need to remove any part of the request that is above the origin.
*/
if (min_top < 0)
min_top = 0;
if (natural_top < 0)
natural_top = 0;
if (min_bottom < 0)
min_bottom = 0;
if (natural_bottom < 0)
natural_bottom = 0;
g_assert (min_bottom >= min_top);
g_assert (natural_bottom >= natural_top);
if (min_height_p)
*min_height_p = min_bottom - min_top;
if (natural_height_p)
*natural_height_p = natural_bottom - min_top;
}
static void
clutter_fixed_layout_allocate (GList *children,
gboolean absolute_origin_changed)
{
GList *l;
for (l = children; l != NULL; l = l->next)
{
ClutterActor *child = l->data;
clutter_actor_allocate_preferred_size (child, absolute_origin_changed);
}
}
static void
clutter_group_get_preferred_width (ClutterActor *self,
gfloat for_height,
gfloat *min_width_p,
gfloat *natural_width_p)
{
ClutterGroupPrivate *priv = CLUTTER_GROUP (self)->priv;
/* for_height is irrelevant to the fixed layout, so it's not used */
clutter_fixed_layout_get_preferred_width (priv->children,
min_width_p,
natural_width_p);
}
static void
clutter_group_get_preferred_height (ClutterActor *self,
gfloat for_width,
gfloat *min_height_p,
gfloat *natural_height_p)
{
ClutterGroupPrivate *priv = CLUTTER_GROUP (self)->priv;
/* for_width is irrelevant to the fixed layout, so it's not used */
clutter_fixed_layout_get_preferred_height (priv->children,
min_height_p,
natural_height_p);
}
static void
clutter_group_allocate (ClutterActor *self,
const ClutterActorBox *box,
gboolean origin_changed)
{
ClutterGroupPrivate *priv = CLUTTER_GROUP (self)->priv;
/* chain up to set actor->allocation */
CLUTTER_ACTOR_CLASS (clutter_group_parent_class)->allocate (self, box,
origin_changed);
/* Note that fixed-layout allocation of children does not care what
* allocation the container received, so "box" is not passed in
* here. We do not require that children's allocations are completely
* contained by our own.
*/
clutter_fixed_layout_allocate (priv->children, origin_changed);
}
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;
}
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 void
clutter_group_real_add (ClutterContainer *container,
ClutterActor *actor)
{
ClutterGroup *group = CLUTTER_GROUP (container);
ClutterGroupPrivate *priv = group->priv;
g_object_ref (actor);
priv->children = g_list_append (priv->children, actor);
clutter_actor_set_parent (actor, CLUTTER_ACTOR (group));
/* queue a relayout, to get the correct positioning inside
* the ::actor-added signal handlers
*/
clutter_actor_queue_relayout (CLUTTER_ACTOR (group));
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)
{
ClutterGroup *group = CLUTTER_GROUP (container);
ClutterGroupPrivate *priv = group->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 (group));
/* 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);
if (CLUTTER_ACTOR_IS_VISIBLE (CLUTTER_ACTOR (group)))
clutter_actor_queue_redraw (CLUTTER_ACTOR (group));
g_object_unref (actor);
}
static void
clutter_group_real_foreach (ClutterContainer *container,
ClutterCallback callback,
gpointer user_data)
{
ClutterGroup *group = CLUTTER_GROUP (container);
ClutterGroupPrivate *priv = group->priv;
GList *l;
for (l = priv->children; l; l = l->next)
(* callback) (CLUTTER_ACTOR (l->data), user_data);
}
static void
clutter_group_real_raise (ClutterContainer *container,
ClutterActor *actor,
ClutterActor *sibling)
{
ClutterGroup *self = CLUTTER_GROUP (container);
ClutterGroupPrivate *priv = self->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 pos;
pos = g_list_index (priv->children, sibling) + 1;
priv->children = g_list_insert (priv->children, actor, pos);
}
/* 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));
}
if (CLUTTER_ACTOR_IS_VISIBLE (container))
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 pos;
pos = g_list_index (priv->children, sibling);
priv->children = g_list_insert (priv->children, actor, pos);
}
/* 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));
}
if (CLUTTER_ACTOR_IS_VISIBLE (container))
clutter_actor_queue_redraw (CLUTTER_ACTOR (container));
}
static gint
sort_z_order (gconstpointer a,
gconstpointer b)
{
int depth_a, depth_b;
depth_a = clutter_actor_get_depth (CLUTTER_ACTOR(a));
depth_b = clutter_actor_get_depth (CLUTTER_ACTOR(b));
return (depth_a - depth_b);
}
static void
clutter_group_real_sort_depth_order (ClutterContainer *container)
{
ClutterGroup *self = CLUTTER_GROUP (container);
ClutterGroupPrivate *priv = self->priv;
priv->children = g_list_sort (priv->children, sort_z_order);
if (CLUTTER_ACTOR_IS_VISIBLE (self))
clutter_actor_queue_redraw (CLUTTER_ACTOR (self));
}
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_class_init (ClutterGroupClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
g_type_class_add_private (object_class, sizeof (ClutterGroupPrivate));
object_class->dispose = clutter_group_dispose;
actor_class->paint = clutter_group_paint;
actor_class->pick = clutter_group_pick;
actor_class->show_all = clutter_group_real_show_all;
actor_class->hide_all = clutter_group_real_hide_all;
actor_class->get_preferred_width = clutter_group_get_preferred_width;
actor_class->get_preferred_height = clutter_group_get_preferred_height;
actor_class->allocate = clutter_group_allocate;
}
static void
clutter_group_init (ClutterGroup *self)
{
self->priv = CLUTTER_GROUP_GET_PRIVATE (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_);
}