diff --git a/.gitignore b/.gitignore index cfecd86ca..71c402306 100644 --- a/.gitignore +++ b/.gitignore @@ -132,6 +132,9 @@ TAGS /tests/interactive/redhand_alpha.png /tests/interactive/test-script.json /tests/interactive/test-clutter-cairo-flowers +/tests/interactive/test-bin-layout +/tests/interactive/test-flow-layout +/tests/interactive/test-box-layout /tests/conform/stamp-test-conformance /tests/conform/test-anchors /tests/conform/test-conformance diff --git a/clutter/Makefile.am b/clutter/Makefile.am index 8348b1040..6b162dc99 100644 --- a/clutter/Makefile.am +++ b/clutter/Makefile.am @@ -64,6 +64,9 @@ source_h = \ $(srcdir)/clutter-behaviour-rotate.h \ $(srcdir)/clutter-behaviour-scale.h \ $(srcdir)/clutter-binding-pool.h \ + $(srcdir)/clutter-bin-layout.h \ + $(srcdir)/clutter-box.h \ + $(srcdir)/clutter-box-layout.h \ $(srcdir)/clutter-cairo-texture.h \ $(srcdir)/clutter-child-meta.h \ $(srcdir)/clutter-clone.h \ @@ -73,10 +76,14 @@ source_h = \ $(srcdir)/clutter-event.h \ $(srcdir)/clutter-feature.h \ $(srcdir)/clutter-fixed.h \ + $(srcdir)/clutter-fixed-layout.h \ + $(srcdir)/clutter-flow-layout.h \ $(srcdir)/clutter-frame-source.h \ $(srcdir)/clutter-group.h \ $(srcdir)/clutter-interval.h \ $(srcdir)/clutter-keysyms.h \ + $(srcdir)/clutter-layout-manager.h \ + $(srcdir)/clutter-layout-meta.h \ $(srcdir)/clutter-list-model.h \ $(srcdir)/clutter-main.h \ $(srcdir)/clutter-media.h \ @@ -128,6 +135,9 @@ source_c = \ $(srcdir)/clutter-behaviour-scale.c \ $(srcdir)/clutter-bezier.c \ $(srcdir)/clutter-binding-pool.c \ + $(srcdir)/clutter-bin-layout.c \ + $(srcdir)/clutter-box.c \ + $(srcdir)/clutter-box-layout.c \ $(srcdir)/clutter-cairo-texture.c \ $(srcdir)/clutter-child-meta.c \ $(srcdir)/clutter-clone.c \ @@ -137,10 +147,14 @@ source_c = \ $(srcdir)/clutter-event.c \ $(srcdir)/clutter-feature.c \ $(srcdir)/clutter-fixed.c \ + $(srcdir)/clutter-fixed-layout.c \ + $(srcdir)/clutter-flow-layout.c \ $(srcdir)/clutter-frame-source.c \ $(srcdir)/clutter-group.c \ $(srcdir)/clutter-id-pool.c \ $(srcdir)/clutter-interval.c \ + $(srcdir)/clutter-layout-manager.c \ + $(srcdir)/clutter-layout-meta.c \ $(srcdir)/clutter-list-model.c \ $(srcdir)/clutter-main.c \ clutter-marshal.c \ diff --git a/clutter/clutter-actor.c b/clutter/clutter-actor.c index 652365a80..6d75c3c5e 100644 --- a/clutter/clutter-actor.c +++ b/clutter/clutter-actor.c @@ -473,8 +473,6 @@ static void clutter_actor_set_natural_width_set (ClutterActor *self, gboolean use_natural_width); static void clutter_actor_set_natural_height_set (ClutterActor *self, gboolean use_natural_height); -static void clutter_actor_set_request_mode (ClutterActor *self, - ClutterRequestMode mode); static void clutter_actor_update_map_state (ClutterActor *self, MapStateChange change); static void clutter_actor_unrealize_not_hiding (ClutterActor *self); @@ -3297,7 +3295,7 @@ clutter_actor_class_init (ClutterActorClass *klass) * gfloat natural_width, min_width; * gfloat natural_height, min_height; * - * g_object_get (G_OBJECT (child), "request-mode", &mode, NULL); + * mode = clutter_actor_get_request_mode (child); * if (mode == CLUTTER_REQUEST_HEIGHT_FOR_WIDTH) * { * clutter_actor_get_preferred_width (child, -1, @@ -5184,11 +5182,28 @@ clutter_actor_set_natural_height_set (ClutterActor *self, clutter_actor_queue_relayout (self); } -static void -clutter_actor_set_request_mode (ClutterActor *self, - ClutterRequestMode mode) +/** + * clutter_actor_set_request_mode: + * @self: a #ClutterActor + * @mode: the request mode + * + * Sets the geometry request mode of @self. + * + * The @mode determines the order for invoking + * clutter_actor_get_preferred_width() and + * clutter_actor_get_preferred_height() + * + * Since: 1.2 + */ +void +clutter_actor_set_request_mode (ClutterActor *self, + ClutterRequestMode mode) { - ClutterActorPrivate *priv = self->priv; + ClutterActorPrivate *priv; + + g_return_if_fail (CLUTTER_IS_ACTOR (self)); + + priv = self->priv; if (priv->request_mode == mode) return; @@ -5203,6 +5218,25 @@ clutter_actor_set_request_mode (ClutterActor *self, clutter_actor_queue_relayout (self); } +/** + * clutter_actor_get_request_mode: + * @self: a #ClutterActor + * + * Retrieves the geometry request mode of @self + * + * Return value: the request mode for the actor + * + * Since: 1.2 + */ +ClutterRequestMode +clutter_actor_get_request_mode (ClutterActor *self) +{ + g_return_val_if_fail (CLUTTER_IS_ACTOR (self), + CLUTTER_REQUEST_HEIGHT_FOR_WIDTH); + + return self->priv->request_mode; +} + /* variant of set_width() without checks and without notification * freeze+thaw, for internal usage only */ diff --git a/clutter/clutter-actor.h b/clutter/clutter-actor.h index d3e28a91b..4a5a38319 100644 --- a/clutter/clutter-actor.h +++ b/clutter/clutter-actor.h @@ -301,6 +301,9 @@ void clutter_actor_queue_relayout (ClutterActor void clutter_actor_destroy (ClutterActor *self); /* size negotiation */ +void clutter_actor_set_request_mode (ClutterActor *self, + ClutterRequestMode mode); +ClutterRequestMode clutter_actor_get_request_mode (ClutterActor *self); void clutter_actor_get_preferred_width (ClutterActor *self, gfloat for_height, gfloat *min_width_p, diff --git a/clutter/clutter-bin-layout.c b/clutter/clutter-bin-layout.c new file mode 100644 index 000000000..80100feda --- /dev/null +++ b/clutter/clutter-bin-layout.c @@ -0,0 +1,865 @@ +/* + * Clutter. + * + * An OpenGL based 'interactive canvas' library. + * + * Copyright (C) 2009 Intel Corporation. + * + * 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 . + * + * Author: + * Emmanuele Bassi + */ + +/** + * SECTION:clutter-bin-layout + * @short_description: A simple layout manager + * + * #ClutterBinLayout is a layout manager which implements the following + * policy: + * + * + * the preferred size is the maximum preferred size + * between all the children of the container using the + * layout; + * each child is allocated in "layers", on on top + * of the other; + * for each layer there are horizontal and vertical + * alignment policies. + * + * + * + * How to pack actors inside a BinLayout + * The following code shows how to build a composite actor with + * a texture and a background, and add controls overlayed on top. The + * background is set to fill the whole allocation, whilst the texture + * is centered; there is a control in the top right corner and a label + * in the bottom, filling out the whole allocated width. + * + * ClutterLayoutManager *manager; + * ClutterActor *box; + * + * /* create the layout first */ + * layout = clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_CENTER, + * CLUTTER_BIN_ALIGNMENT_CENTER); + * box = clutter_box_new (layout); /* then the container */ + * + * /* we can use the layout object to add actors */ + * clutter_bin_layout_add (CLUTTER_BIN_LAYOUT (layout), background, + * CLUTTER_BIN_ALIGNMENT_FILL, + * CLUTTER_BIN_ALIGNMENT_FILL); + * clutter_bin_layout_add (CLUTTER_BIN_LAYOUT (layout), icon, + * CLUTTER_BIN_ALIGNMENT_CENTER, + * CLUTTER_BIN_ALIGNMENT_CENTER); + * + * /* align to the bottom left */ + * clutter_bin_layout_add (CLUTTER_BIN_LAYOUT (layout), label, + * CLUTTER_BIN_ALIGNMENT_START, + * CLUTTER_BIN_ALIGNMENT_END); + * /* align to the top right */ + * clutter_bin_layout_add (CLUTTER_BIN_LAYOUT (layout), button, + * CLUTTER_BIN_ALIGNMENT_END, + * CLUTTER_BIN_ALIGNMENT_START); + * + * + * + * #ClutterBinLayout is available since Clutter 1.2 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "clutter-actor.h" +#include "clutter-animatable.h" +#include "clutter-bin-layout.h" +#include "clutter-child-meta.h" +#include "clutter-debug.h" +#include "clutter-enum-types.h" +#include "clutter-layout-meta.h" +#include "clutter-private.h" + +#define CLUTTER_TYPE_BIN_LAYER (clutter_bin_layer_get_type ()) +#define CLUTTER_BIN_LAYER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CLUTTER_TYPE_BIN_LAYER, ClutterBinLayer)) +#define CLUTTER_IS_BIN_LAYER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CLUTTER_TYPE_BIN_LAYER)) + +#define CLUTTER_BIN_LAYOUT_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLUTTER_TYPE_BIN_LAYOUT, ClutterBinLayoutPrivate)) + +typedef struct _ClutterBinLayer ClutterBinLayer; +typedef struct _ClutterLayoutMetaClass ClutterBinLayerClass; + +struct _ClutterBinLayoutPrivate +{ + ClutterBinAlignment x_align; + ClutterBinAlignment y_align; + + ClutterContainer *container; +}; + +struct _ClutterBinLayer +{ + ClutterLayoutMeta parent_instance; + + ClutterBinAlignment x_align; + ClutterBinAlignment y_align; +}; + +enum +{ + PROP_LAYER_0, + + PROP_LAYER_X_ALIGN, + PROP_LAYER_Y_ALIGN +}; + +enum +{ + PROP_0, + + PROP_X_ALIGN, + PROP_Y_ALIGN +}; + +G_DEFINE_TYPE (ClutterBinLayer, + clutter_bin_layer, + CLUTTER_TYPE_LAYOUT_META); + +G_DEFINE_TYPE (ClutterBinLayout, + clutter_bin_layout, + CLUTTER_TYPE_LAYOUT_MANAGER); + +/* + * ClutterBinLayer + */ + +static void +set_layer_x_align (ClutterBinLayer *self, + ClutterBinAlignment alignment) +{ + ClutterLayoutManager *manager; + ClutterLayoutMeta *meta; + + if (self->x_align == alignment) + return; + + self->x_align = alignment; + + meta = CLUTTER_LAYOUT_META (self); + manager = clutter_layout_meta_get_manager (meta); + clutter_layout_manager_layout_changed (manager); + + g_object_notify (G_OBJECT (self), "x-align"); +} + +static void +set_layer_y_align (ClutterBinLayer *self, + ClutterBinAlignment alignment) +{ + ClutterLayoutManager *manager; + ClutterLayoutMeta *meta; + + if (self->y_align == alignment) + return; + + self->y_align = alignment; + + meta = CLUTTER_LAYOUT_META (self); + manager = clutter_layout_meta_get_manager (meta); + clutter_layout_manager_layout_changed (manager); + + g_object_notify (G_OBJECT (self), "y-align"); +} + +static void +clutter_bin_layer_set_property (GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + ClutterBinLayer *layer = CLUTTER_BIN_LAYER (gobject); + + switch (prop_id) + { + case PROP_LAYER_X_ALIGN: + set_layer_x_align (layer, g_value_get_enum (value)); + break; + + case PROP_LAYER_Y_ALIGN: + set_layer_y_align (layer, g_value_get_enum (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +clutter_bin_layer_get_property (GObject *gobject, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + ClutterBinLayer *layer = CLUTTER_BIN_LAYER (gobject); + + switch (prop_id) + { + case PROP_LAYER_X_ALIGN: + g_value_set_enum (value, layer->x_align); + break; + + case PROP_LAYER_Y_ALIGN: + g_value_set_enum (value, layer->y_align); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +clutter_bin_layer_class_init (ClutterBinLayerClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GParamSpec *pspec; + + gobject_class->set_property = clutter_bin_layer_set_property; + gobject_class->get_property = clutter_bin_layer_get_property; + + pspec = g_param_spec_enum ("x-align", + "Horizontal Alignment", + "Horizontal alignment for the actor " + "inside the layer", + CLUTTER_TYPE_BIN_ALIGNMENT, + CLUTTER_BIN_ALIGNMENT_CENTER, + CLUTTER_PARAM_READWRITE); + g_object_class_install_property (gobject_class, + PROP_LAYER_X_ALIGN, + pspec); + + pspec = g_param_spec_enum ("y-align", + "Vertical Alignment", + "Vertical alignment for the actor " + "inside the layer manager", + CLUTTER_TYPE_BIN_ALIGNMENT, + CLUTTER_BIN_ALIGNMENT_CENTER, + CLUTTER_PARAM_READWRITE); + g_object_class_install_property (gobject_class, + PROP_LAYER_Y_ALIGN, + pspec); +} + +static void +clutter_bin_layer_init (ClutterBinLayer *layer) +{ + layer->x_align = CLUTTER_BIN_ALIGNMENT_CENTER; + layer->y_align = CLUTTER_BIN_ALIGNMENT_CENTER; +} + +/* + * ClutterBinLayout + */ + +static void +set_x_align (ClutterBinLayout *self, + ClutterBinAlignment alignment) +{ + ClutterBinLayoutPrivate *priv = self->priv; + + if (priv->x_align != alignment) + { + ClutterLayoutManager *manager; + + priv->x_align = alignment; + + manager = CLUTTER_LAYOUT_MANAGER (self); + clutter_layout_manager_layout_changed (manager); + + g_object_notify (G_OBJECT (self), "x-align"); + } +} + +static void +set_y_align (ClutterBinLayout *self, + ClutterBinAlignment alignment) +{ + ClutterBinLayoutPrivate *priv = self->priv; + + if (priv->y_align != alignment) + { + ClutterLayoutManager *manager; + + priv->y_align = alignment; + + manager = CLUTTER_LAYOUT_MANAGER (self); + clutter_layout_manager_layout_changed (manager); + + g_object_notify (G_OBJECT (self), "y-align"); + } +} + +static void +clutter_bin_layout_get_preferred_width (ClutterLayoutManager *manager, + ClutterContainer *container, + gfloat for_height, + gfloat *min_width_p, + gfloat *nat_width_p) +{ + GList *children = clutter_container_get_children (container); + GList *l; + gfloat min_width, nat_width; + + min_width = nat_width = 0.0; + + for (l = children; l != NULL; l = l->next) + { + ClutterActor *child = l->data; + gfloat minimum, natural; + + clutter_actor_get_preferred_width (child, for_height, + &minimum, + &natural); + + min_width = MAX (min_width, minimum); + nat_width = MAX (nat_width, natural); + } + + if (min_width_p) + *min_width_p = min_width; + + if (nat_width_p) + *nat_width_p = nat_width; +} + +static void +clutter_bin_layout_get_preferred_height (ClutterLayoutManager *manager, + ClutterContainer *container, + gfloat for_width, + gfloat *min_height_p, + gfloat *nat_height_p) +{ + GList *children = clutter_container_get_children (container); + GList *l; + gfloat min_height, nat_height; + + min_height = nat_height = 0.0; + + for (l = children; l != NULL; l = l->next) + { + ClutterActor *child = l->data; + gfloat minimum, natural; + + clutter_actor_get_preferred_height (child, for_width, + &minimum, + &natural); + + min_height = MAX (min_height, minimum); + nat_height = MAX (nat_height, natural); + } + + if (min_height_p) + *min_height_p = min_height; + + if (nat_height_p) + *nat_height_p = nat_height; +} + +static gdouble +get_bin_alignment_factor (ClutterBinAlignment alignment) +{ + switch (alignment) + { + case CLUTTER_BIN_ALIGNMENT_CENTER: + return 0.5; + + case CLUTTER_BIN_ALIGNMENT_START: + return 0.0; + + case CLUTTER_BIN_ALIGNMENT_END: + return 1.0; + + case CLUTTER_BIN_ALIGNMENT_FIXED: + case CLUTTER_BIN_ALIGNMENT_FILL: + return 0.0; + } + + return 0.0; +} + +static void +clutter_bin_layout_allocate (ClutterLayoutManager *manager, + ClutterContainer *container, + const ClutterActorBox *allocation, + ClutterAllocationFlags flags) +{ + GList *children = clutter_container_get_children (container); + GList *l; + gfloat available_w, available_h; + + available_w = clutter_actor_box_get_width (allocation); + available_h = clutter_actor_box_get_height (allocation); + + for (l = children; l != NULL; l = l->next) + { + ClutterActor *child = l->data; + ClutterLayoutMeta *meta; + ClutterBinLayer *layer; + ClutterActorBox child_alloc = { 0, }; + gfloat child_width, child_height; + ClutterRequestMode request; + + meta = clutter_layout_manager_get_child_meta (manager, + container, + child); + layer = CLUTTER_BIN_LAYER (meta); + + if (layer->x_align == CLUTTER_BIN_ALIGNMENT_FILL) + { + child_alloc.x1 = 0; + child_alloc.x2 = ceilf (available_w); + } + + if (layer->y_align == CLUTTER_BIN_ALIGNMENT_FILL) + { + child_alloc.y1 = 0; + child_alloc.y2 = ceilf (available_h); + } + + /* if we are filling horizontally and vertically then we + * can break here because we already have a full allocation + */ + if (layer->x_align == CLUTTER_BIN_ALIGNMENT_FILL && + layer->y_align == CLUTTER_BIN_ALIGNMENT_FILL) + { + clutter_actor_allocate (child, &child_alloc, flags); + continue; + } + + request = clutter_actor_get_request_mode (child); + if (request == CLUTTER_REQUEST_HEIGHT_FOR_WIDTH) + { + gfloat min_width, nat_width; + gfloat min_height, nat_height; + + clutter_actor_get_preferred_width (child, available_h, + &min_width, + &nat_width); + child_width = CLAMP (nat_width, min_width, available_w); + + clutter_actor_get_preferred_height (child, child_width, + &min_height, + &nat_height); + child_height = CLAMP (nat_height, min_height, available_h); + } + else if (request == CLUTTER_REQUEST_WIDTH_FOR_HEIGHT) + { + gfloat min_width, nat_width; + gfloat min_height, nat_height; + + clutter_actor_get_preferred_height (child, available_w, + &min_height, + &nat_height); + child_height = CLAMP (nat_height, min_height, available_h); + + clutter_actor_get_preferred_width (child, child_height, + &min_width, + &nat_width); + child_width = CLAMP (nat_width, min_width, available_w); + } + + if (layer->x_align == CLUTTER_BIN_ALIGNMENT_FIXED) + { + child_alloc.x1 = ceilf (clutter_actor_get_x (child)); + child_alloc.x2 = ceilf (child_alloc.x1 + child_width); + } + else + { + gdouble x_align = get_bin_alignment_factor (layer->x_align); + + if (layer->x_align != CLUTTER_BIN_ALIGNMENT_FILL) + { + child_alloc.x1 = ceilf ((available_w - child_width) * x_align); + child_alloc.x2 = ceilf (child_alloc.x1 + child_width); + } + } + + if (layer->y_align == CLUTTER_BIN_ALIGNMENT_FIXED) + { + child_alloc.y1 = ceilf (clutter_actor_get_y (child)); + child_alloc.y2 = ceilf (child_alloc.y1 + child_height); + } + else + { + gdouble y_align = get_bin_alignment_factor (layer->y_align); + + if (layer->y_align != CLUTTER_BIN_ALIGNMENT_FILL) + { + child_alloc.y1 = ceilf ((available_h - child_height) * y_align); + child_alloc.y2 = ceilf (child_alloc.y1 + child_height); + } + } + + clutter_actor_allocate (child, &child_alloc, flags); + } + + g_list_free (children); +} + +static ClutterLayoutMeta * +clutter_bin_layout_create_child_meta (ClutterLayoutManager *manager, + ClutterContainer *container, + ClutterActor *actor) +{ + ClutterBinLayoutPrivate *priv; + + priv = CLUTTER_BIN_LAYOUT (manager)->priv; + + return g_object_new (CLUTTER_TYPE_BIN_LAYER, + "container", container, + "actor", actor, + "manager", manager, + "x-align", priv->x_align, + "y_align", priv->y_align, + NULL); +} + +static void +clutter_bin_layout_set_container (ClutterLayoutManager *manager, + ClutterContainer *container) +{ + ClutterBinLayoutPrivate *priv; + + priv = CLUTTER_BIN_LAYOUT (manager)->priv; + priv->container = container; +} + +static void +clutter_bin_layout_set_property (GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + ClutterBinLayout *layout = CLUTTER_BIN_LAYOUT (gobject); + + switch (prop_id) + { + case PROP_X_ALIGN: + set_x_align (layout, g_value_get_enum (value)); + break; + + case PROP_Y_ALIGN: + set_y_align (layout, g_value_get_enum (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +clutter_bin_layout_get_property (GObject *gobject, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + ClutterBinLayoutPrivate *priv; + + priv = CLUTTER_BIN_LAYOUT (gobject)->priv; + + switch (prop_id) + { + case PROP_X_ALIGN: + g_value_set_enum (value, priv->x_align); + break; + + case PROP_Y_ALIGN: + g_value_set_enum (value, priv->y_align); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +clutter_bin_layout_class_init (ClutterBinLayoutClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + ClutterLayoutManagerClass *layout_class = + CLUTTER_LAYOUT_MANAGER_CLASS (klass); + GParamSpec *pspec; + + g_type_class_add_private (klass, sizeof (ClutterBinLayoutPrivate)); + + gobject_class->set_property = clutter_bin_layout_set_property; + gobject_class->get_property = clutter_bin_layout_get_property; + + /** + * ClutterBinLayout:x-align: + * + * The default horizontal alignment policy for actors managed + * by the #ClutterBinLayout + * + * Since: 1.2 + */ + pspec = g_param_spec_enum ("x-align", + "Horizontal Alignment", + "Default horizontal alignment for the actors " + "inside the layout manager", + CLUTTER_TYPE_BIN_ALIGNMENT, + CLUTTER_BIN_ALIGNMENT_CENTER, + CLUTTER_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_X_ALIGN, pspec); + + /** + * ClutterBinLayout:y-align: + * + * The default vertical alignment policy for actors managed + * by the #ClutterBinLayout + * + * Since: 1.2 + */ + pspec = g_param_spec_enum ("y-align", + "Vertical Alignment", + "Default vertical alignment for the actors " + "inside the layout manager", + CLUTTER_TYPE_BIN_ALIGNMENT, + CLUTTER_BIN_ALIGNMENT_CENTER, + CLUTTER_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_Y_ALIGN, pspec); + + layout_class->get_preferred_width = + clutter_bin_layout_get_preferred_width; + layout_class->get_preferred_height = + clutter_bin_layout_get_preferred_height; + layout_class->allocate = + clutter_bin_layout_allocate; + layout_class->create_child_meta = + clutter_bin_layout_create_child_meta; + layout_class->set_container = + clutter_bin_layout_set_container; +} + +static void +clutter_bin_layout_init (ClutterBinLayout *self) +{ + self->priv = CLUTTER_BIN_LAYOUT_GET_PRIVATE (self); + + self->priv->x_align = CLUTTER_BIN_ALIGNMENT_CENTER; + self->priv->y_align = CLUTTER_BIN_ALIGNMENT_CENTER; +} + +/** + * clutter_bin_layout_new: + * @x_align: the default alignment policy to be used on the + * horizontal axis + * @y_align: the default alignment policy to be used on the + * vertical axis + * + * Creates a new #ClutterBinLayout layout manager + * + * Return value: the newly created layout manager + * + * Since: 1.2 + */ +ClutterLayoutManager * +clutter_bin_layout_new (ClutterBinAlignment x_align, + ClutterBinAlignment y_align) +{ + return g_object_new (CLUTTER_TYPE_BIN_LAYOUT, + "x-align", x_align, + "y-align", y_align, + NULL); +} + +/** + * clutter_bin_layout_set_alignment: + * @self: a #ClutterBinLayout + * @child: (allow-none): a child of @container + * @x_align: the horizontal alignment policy to be used for the @child + * inside @container + * @y_align: the vertical aligment policy to be used on the @child + * inside @container + * + * Sets the horizontal and vertical alignment policies to be applied + * to a @child of @self + * + * If @child is %NULL then the @x_align and @y_align values will + * be set as the default alignment policies + * + * Since: 1.2 + */ +void +clutter_bin_layout_set_alignment (ClutterBinLayout *self, + ClutterActor *child, + ClutterBinAlignment x_align, + ClutterBinAlignment y_align) +{ + ClutterBinLayoutPrivate *priv; + ClutterLayoutManager *manager; + ClutterLayoutMeta *meta; + + g_return_if_fail (CLUTTER_IS_BIN_LAYOUT (self)); + g_return_if_fail (child == NULL || CLUTTER_IS_ACTOR (child)); + + priv = self->priv; + + if (priv->container == NULL) + { + if (child == NULL) + { + set_x_align (self, x_align); + set_y_align (self, y_align); + } + else + g_warning ("The layout of type '%s' must be associated to " + "a ClutterContainer before setting the alignment " + "on its children", + G_OBJECT_TYPE_NAME (self)); + + return; + } + + manager = CLUTTER_LAYOUT_MANAGER (self); + meta = clutter_layout_manager_get_child_meta (manager, + priv->container, + child); + g_assert (CLUTTER_IS_BIN_LAYER (meta)); + + set_layer_x_align (CLUTTER_BIN_LAYER (meta), x_align); + set_layer_y_align (CLUTTER_BIN_LAYER (meta), y_align); +} + +/** + * clutter_bin_layout_get_alignment: + * @self: a #ClutterBinLayout + * @child: (allow-none): a child of @container + * @x_align: (out) (allow-none): return location for the horizontal + * alignment policy + * @y_align: (out) (allow-none): return location for the vertical + * alignment policy + * + * Retrieves the horizontal and vertical alignment policies for + * a child of @self + * + * If @child is %NULL the default alignment policies will be returned + * instead + * + * Since: 1.2 + */ +void +clutter_bin_layout_get_alignment (ClutterBinLayout *self, + ClutterActor *child, + ClutterBinAlignment *x_align, + ClutterBinAlignment *y_align) +{ + ClutterBinLayoutPrivate *priv; + ClutterLayoutManager *manager; + ClutterLayoutMeta *meta; + ClutterBinLayer *layer; + + g_return_if_fail (CLUTTER_IS_BIN_LAYOUT (self)); + + priv = self->priv; + + if (priv->container == NULL) + { + if (child == NULL) + { + if (x_align) + *x_align = priv->x_align; + + if (y_align) + *y_align = priv->y_align; + } + else + g_warning ("The layout of type '%s' must be associated to " + "a ClutterContainer before getting the alignment " + "of its children", + G_OBJECT_TYPE_NAME (self)); + + return; + } + + manager = CLUTTER_LAYOUT_MANAGER (self); + meta = clutter_layout_manager_get_child_meta (manager, + priv->container, + child); + g_assert (CLUTTER_IS_BIN_LAYER (meta)); + + layer = CLUTTER_BIN_LAYER (meta); + + if (x_align) + *x_align = layer->x_align; + + if (y_align) + *y_align = layer->y_align; +} + +/** + * clutter_bin_layout_add: + * @self: a #ClutterBinLayout + * @child: a #ClutterActor + * @x_align: horizontal alignment policy for @child + * @y_align: vertical alignment policy for @child + * + * Adds a #ClutterActor to the container using @self and + * sets the alignment policies for it + * + * This function is equivalent to clutter_container_add_actor() + * and clutter_layout_manager_child_set_property() but it does not + * require a pointer to the #ClutterContainer associated to the + * #ClutterBinLayout + * + * Since: 1.2 + */ +void +clutter_bin_layout_add (ClutterBinLayout *self, + ClutterActor *child, + ClutterBinAlignment x_align, + ClutterBinAlignment y_align) +{ + ClutterBinLayoutPrivate *priv; + ClutterLayoutManager *manager; + ClutterLayoutMeta *meta; + + g_return_if_fail (CLUTTER_IS_BIN_LAYOUT (self)); + g_return_if_fail (CLUTTER_IS_ACTOR (child)); + + priv = self->priv; + + if (priv->container == NULL) + { + g_warning ("The layout of type '%s' must be associated to " + "a ClutterContainer before adding children", + G_OBJECT_TYPE_NAME (self)); + return; + } + + clutter_container_add_actor (priv->container, child); + + manager = CLUTTER_LAYOUT_MANAGER (self); + meta = clutter_layout_manager_get_child_meta (manager, + priv->container, + child); + g_assert (CLUTTER_IS_BIN_LAYER (meta)); + + set_layer_x_align (CLUTTER_BIN_LAYER (meta), x_align); + set_layer_y_align (CLUTTER_BIN_LAYER (meta), y_align); +} diff --git a/clutter/clutter-bin-layout.h b/clutter/clutter-bin-layout.h new file mode 100644 index 000000000..169af0774 --- /dev/null +++ b/clutter/clutter-bin-layout.h @@ -0,0 +1,123 @@ +/* + * Clutter. + * + * An OpenGL based 'interactive canvas' library. + * + * Copyright (C) 2009 Intel Corporation. + * + * 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 . + * + * Author: + * Emmanuele Bassi + */ + +#if !defined(__CLUTTER_H_INSIDE__) && !defined(CLUTTER_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __CLUTTER_BIN_LAYOUT_H__ +#define __CLUTTER_BIN_LAYOUT_H__ + +#include + +G_BEGIN_DECLS + +#define CLUTTER_TYPE_BIN_LAYOUT (clutter_bin_layout_get_type ()) +#define CLUTTER_BIN_LAYOUT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CLUTTER_TYPE_BIN_LAYOUT, ClutterBinLayout)) +#define CLUTTER_IS_BIN_LAYOUT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CLUTTER_TYPE_BIN_LAYOUT)) +#define CLUTTER_BIN_LAYOUT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CLUTTER_TYPE_BIN_LAYOUT, ClutterBinLayoutClass)) +#define CLUTTER_IS_BIN_LAYOUT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CLUTTER_TYPE_BIN_LAYOUT)) +#define CLUTTER_BIN_LAYOUT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CLUTTER_TYPE_BIN_LAYOUT, ClutterBinLayoutClass)) + +typedef struct _ClutterBinLayout ClutterBinLayout; +typedef struct _ClutterBinLayoutPrivate ClutterBinLayoutPrivate; +typedef struct _ClutterBinLayoutClass ClutterBinLayoutClass; + +/** + * ClutterBinAlignment: + * @CLUTTER_BIN_ALIGNMENT_FIXED: Fixed position alignment; the + * #ClutterBinLayout will honour the fixed position provided + * by the actors themselves when allocating them + * @CLUTTER_BIN_ALIGNMENT_FILL: Fill the allocation size + * @CLUTTER_BIN_ALIGNMENT_START: Position the actors at the top + * or left side of the container, depending on the axis + * @CLUTTER_BIN_ALIGNMENT_END: Position the actors at the bottom + * or right side of the container, depending on the axis + * @CLUTTER_BIN_ALIGNMENT_CENTER: Position the actors at the + * center of the container, depending on the axis + * + * The alignment policies available on each axis for #ClutterBinLayout + * + * Since: 1.2 + */ +typedef enum { + CLUTTER_BIN_ALIGNMENT_FIXED, + CLUTTER_BIN_ALIGNMENT_FILL, + CLUTTER_BIN_ALIGNMENT_START, + CLUTTER_BIN_ALIGNMENT_END, + CLUTTER_BIN_ALIGNMENT_CENTER +} ClutterBinAlignment; + +/** + * ClutterBinLayout: + * + * The #ClutterBinLayout structure contains only private data + * and should be accessed using the provided API + * + * Since: 1.2 + */ +struct _ClutterBinLayout +{ + /*< private >*/ + ClutterLayoutManager parent_instance; + + ClutterBinLayoutPrivate *priv; +}; + +/** + * ClutterBinLayoutClass: + * + * The #ClutterBinLayoutClass structure contains only private + * data and should be accessed using the provided API + * + * Since: 1.2 + */ +struct _ClutterBinLayoutClass +{ + /*< private >*/ + ClutterLayoutManagerClass parent_class; +}; + +GType clutter_bin_layout_get_type (void) G_GNUC_CONST; + +ClutterLayoutManager *clutter_bin_layout_new (ClutterBinAlignment x_align, + ClutterBinAlignment y_align); + +void clutter_bin_layout_set_alignment (ClutterBinLayout *self, + ClutterActor *child, + ClutterBinAlignment x_align, + ClutterBinAlignment y_align); +void clutter_bin_layout_get_alignment (ClutterBinLayout *self, + ClutterActor *child, + ClutterBinAlignment *x_align, + ClutterBinAlignment *y_align); + +void clutter_bin_layout_add (ClutterBinLayout *self, + ClutterActor *child, + ClutterBinAlignment x_align, + ClutterBinAlignment y_align); + +G_END_DECLS + +#endif /* __CLUTTER_BIN_LAYOUT_H__ */ diff --git a/clutter/clutter-box-layout.c b/clutter/clutter-box-layout.c new file mode 100644 index 000000000..c9371f950 --- /dev/null +++ b/clutter/clutter-box-layout.c @@ -0,0 +1,1496 @@ +/* + * Clutter. + * + * An OpenGL based 'interactive canvas' library. + * + * Copyright (C) 2009 Intel Corporation. + * + * 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 . + * + * Author: + * Emmanuele Bassi + * + * Based on the NBTK NbtkBoxLayout actor by: + * Thomas Wood + */ + +/** + * SECTION:clutter-box-layout + * @short_description: A layout manager arranging children on a single line + * + * The #ClutterBoxLayout is a #ClutterLayoutManager implementing the + * following layout policy: + * + * all children are arranged on a single + * line; + * the axis used is controlled by the + * #ClutterBoxLayout:vertical boolean property; + * the order of the packing is determined by the + * #ClutterBoxLayout:pack-start boolean property; + * each child will be allocated to its natural + * size or, if set to expand, the available size; + * if a child is set to fill on either (or both) + * axis, its allocation will match all the available size; the + * fill layout property only makes sense if the expand property is + * also set; + * if a child is set to expand but not to fill then + * it is possible to control the alignment using the X and Y alignment + * layout properties. + * + * + * It is possible to control the spacing between children of a + * #ClutterBoxLayout by using clutter_box_layout_set_spacing(). + * + * In order to set the layout properties when packing an actor inside a + * #ClutterBoxLayout you should use the clutter_box_layout_pack() + * function. + * + * #ClutterBoxLayout is available since Clutter 1.2 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "clutter-box-layout.h" +#include "clutter-debug.h" +#include "clutter-enum-types.h" +#include "clutter-layout-meta.h" +#include "clutter-private.h" +#include "clutter-types.h" + +#define CLUTTER_TYPE_BOX_CHILD (clutter_box_child_get_type ()) +#define CLUTTER_BOX_CHILD(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CLUTTER_TYPE_BOX_CHILD, ClutterBoxChild)) +#define CLUTTER_IS_BOX_CHILD(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CLUTTER_TYPE_BOX_CHILD)) + +#define CLUTTER_BOX_LAYOUT_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLUTTER_TYPE_BOX_LAYOUT, ClutterBoxLayoutPrivate)) + +typedef struct _ClutterBoxChild ClutterBoxChild; +typedef struct _ClutterLayoutMetaClass ClutterBoxChildClass; + +struct _ClutterBoxLayoutPrivate +{ + ClutterContainer *container; + + guint spacing; + + guint is_vertical : 1; + guint is_pack_start : 1; +}; + +struct _ClutterBoxChild +{ + ClutterLayoutMeta parent_instance; + + ClutterBoxAlignment x_align; + ClutterBoxAlignment y_align; + + guint x_fill : 1; + guint y_fill : 1; + + guint expand : 1; +}; + +enum +{ + PROP_CHILD_0, + + PROP_CHILD_X_ALIGN, + PROP_CHILD_Y_ALIGN, + PROP_CHILD_X_FILL, + PROP_CHILD_Y_FILL, + PROP_CHILD_EXPAND +}; + +enum +{ + PROP_0, + + PROP_SPACING, + PROP_VERTICAL, + PROP_PACK_START +}; + +G_DEFINE_TYPE (ClutterBoxChild, + clutter_box_child, + CLUTTER_TYPE_LAYOUT_META); + +G_DEFINE_TYPE (ClutterBoxLayout, + clutter_box_layout, + CLUTTER_TYPE_LAYOUT_MANAGER); + +/* + * ClutterBoxChild + */ + +static void +box_child_set_align (ClutterBoxChild *self, + ClutterBoxAlignment x_align, + ClutterBoxAlignment y_align) +{ + gboolean x_changed = FALSE, y_changed = FALSE; + + if (self->x_align != x_align) + { + self->x_align = x_align; + + x_changed = TRUE; + } + + if (self->y_align != y_align) + { + self->y_align = y_align; + + y_changed = TRUE; + } + + if (x_changed || y_changed) + { + ClutterLayoutManager *layout; + + layout = clutter_layout_meta_get_manager (CLUTTER_LAYOUT_META (self)); + clutter_layout_manager_layout_changed (layout); + + if (x_changed) + g_object_notify (G_OBJECT (self), "x-align"); + + if (y_changed) + g_object_notify (G_OBJECT (self), "y-align"); + } +} + +static void +box_child_set_fill (ClutterBoxChild *self, + gboolean x_fill, + gboolean y_fill) +{ + gboolean x_changed = FALSE, y_changed = FALSE; + + if (self->x_fill != x_fill) + { + self->x_fill = x_fill; + + x_changed = TRUE; + } + + if (self->y_fill != y_fill) + { + self->y_fill = y_fill; + + y_changed = TRUE; + } + + if (x_changed || y_changed) + { + ClutterLayoutManager *layout; + + layout = clutter_layout_meta_get_manager (CLUTTER_LAYOUT_META (self)); + clutter_layout_manager_layout_changed (layout); + + if (x_changed) + g_object_notify (G_OBJECT (self), "x-fill"); + + if (y_changed) + g_object_notify (G_OBJECT (self), "y-fill"); + } +} + +static void +box_child_set_expand (ClutterBoxChild *self, + gboolean expand) +{ + if (self->expand != expand) + { + ClutterLayoutManager *layout; + + self->expand = expand; + + layout = clutter_layout_meta_get_manager (CLUTTER_LAYOUT_META (self)); + clutter_layout_manager_layout_changed (layout); + + g_object_notify (G_OBJECT (self), "expand"); + } +} + +static void +clutter_box_child_set_property (GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + ClutterBoxChild *self = CLUTTER_BOX_CHILD (gobject); + + switch (prop_id) + { + case PROP_CHILD_X_ALIGN: + box_child_set_align (self, + g_value_get_enum (value), + self->y_align); + break; + + case PROP_CHILD_Y_ALIGN: + box_child_set_align (self, + self->x_align, + g_value_get_enum (value)); + break; + + case PROP_CHILD_X_FILL: + box_child_set_fill (self, + g_value_get_boolean (value), + self->y_fill); + break; + + case PROP_CHILD_Y_FILL: + box_child_set_fill (self, + self->x_fill, + g_value_get_boolean (value)); + break; + + case PROP_CHILD_EXPAND: + box_child_set_expand (self, g_value_get_boolean (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +clutter_box_child_get_property (GObject *gobject, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + ClutterBoxChild *self = CLUTTER_BOX_CHILD (gobject); + + switch (prop_id) + { + case PROP_CHILD_X_ALIGN: + g_value_set_enum (value, self->x_align); + break; + + case PROP_CHILD_Y_ALIGN: + g_value_set_enum (value, self->y_align); + break; + + case PROP_CHILD_X_FILL: + g_value_set_boolean (value, self->x_fill); + break; + + case PROP_CHILD_Y_FILL: + g_value_set_boolean (value, self->y_fill); + break; + + case PROP_CHILD_EXPAND: + g_value_set_boolean (value, self->expand); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +clutter_box_child_class_init (ClutterBoxChildClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GParamSpec *pspec; + + gobject_class->set_property = clutter_box_child_set_property; + gobject_class->get_property = clutter_box_child_get_property; + + pspec = g_param_spec_boolean ("expand", + "Expand", + "Allocate extra space for the child", + FALSE, + CLUTTER_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_CHILD_EXPAND, pspec); + + pspec = g_param_spec_boolean ("x-fill", + "Horizontal Fill", + "Whether the child should receive priority " + "when the container is allocating spare space " + "on the horizontal axis", + FALSE, + CLUTTER_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_CHILD_X_FILL, pspec); + + pspec = g_param_spec_boolean ("y-fill", + "Vertical Fill", + "Whether the child should receive priority " + "when the container is allocating spare space " + "on the vertical axis", + FALSE, + CLUTTER_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_CHILD_Y_FILL, pspec); + + pspec = g_param_spec_enum ("x-align", + "Horizontal Alignment", + "Horizontal alignment of the actor within " + "the cell", + CLUTTER_TYPE_BOX_ALIGNMENT, + CLUTTER_BOX_ALIGNMENT_CENTER, + CLUTTER_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_CHILD_X_ALIGN, pspec); + + pspec = g_param_spec_enum ("y-align", + "Vertical Alignment", + "Vertical alignment of the actor within " + "the cell", + CLUTTER_TYPE_BOX_ALIGNMENT, + CLUTTER_BOX_ALIGNMENT_CENTER, + CLUTTER_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_CHILD_Y_ALIGN, pspec); +} + +static void +clutter_box_child_init (ClutterBoxChild *self) +{ + self->x_align = CLUTTER_BOX_ALIGNMENT_CENTER; + self->y_align = CLUTTER_BOX_ALIGNMENT_CENTER; + + self->x_fill = self->y_fill = FALSE; + + self->expand = FALSE; +} + +static inline void +allocate_fill (ClutterActor *child, + ClutterActorBox *childbox, + ClutterBoxChild *box_child) +{ + gfloat natural_width, natural_height; + gfloat min_width, min_height; + gfloat child_width, child_height; + gfloat available_width, available_height; + ClutterRequestMode request; + ClutterActorBox allocation = { 0, }; + gdouble x_align, y_align; + + if (box_child->x_align == CLUTTER_BOX_ALIGNMENT_START) + x_align = 0.0; + else if (box_child->x_align == CLUTTER_BOX_ALIGNMENT_CENTER) + x_align = 0.5; + else + x_align = 1.0; + + if (box_child->y_align == CLUTTER_BOX_ALIGNMENT_START) + y_align = 0.0; + else if (box_child->y_align == CLUTTER_BOX_ALIGNMENT_CENTER) + y_align = 0.5; + else + y_align = 1.0; + + available_width = childbox->x2 - childbox->x1; + available_height = childbox->y2 - childbox->y1; + + if (available_width < 0) + available_width = 0; + + if (available_height < 0) + available_height = 0; + + if (box_child->x_fill) + { + allocation.x1 = childbox->x1; + allocation.x2 = ceilf (allocation.x1 + available_width); + } + + if (box_child->y_fill) + { + allocation.y1 = childbox->y1; + allocation.y2 = ceilf (allocation.y1 + available_height); + } + + /* if we are filling horizontally and vertically then we're done */ + if (box_child->x_fill && box_child->y_fill) + { + *childbox = allocation; + return; + } + + request = clutter_actor_get_request_mode (child); + if (request == CLUTTER_REQUEST_HEIGHT_FOR_WIDTH) + { + clutter_actor_get_preferred_width (child, available_height, + &min_width, + &natural_width); + + child_width = CLAMP (natural_width, min_width, available_width); + + clutter_actor_get_preferred_height (child, child_width, + &min_height, + &natural_height); + + child_height = CLAMP (natural_height, min_height, available_height); + } + else + { + clutter_actor_get_preferred_height (child, available_width, + &min_height, + &natural_height); + + child_height = CLAMP (natural_height, min_height, available_height); + + clutter_actor_get_preferred_width (child, child_height, + &min_width, + &natural_width); + + child_width = CLAMP (natural_width, min_width, available_width); + } + + if (!box_child->x_fill) + { + allocation.x1 = ceilf (childbox->x1 + + ((available_width - child_width) * x_align)); + allocation.x2 = ceilf (allocation.x1 + child_width); + } + + if (!box_child->y_fill) + { + allocation.y1 = ceilf (childbox->y1 + + ((available_height - child_height) * y_align)); + allocation.y2 = ceilf (allocation.y1 + child_height); + } + + *childbox = allocation; +} + +static ClutterLayoutMeta * +clutter_box_layout_create_child_meta (ClutterLayoutManager *layout, + ClutterContainer *container, + ClutterActor *actor) +{ + return g_object_new (CLUTTER_TYPE_BOX_CHILD, + "manager", layout, + "container", container, + "actor", actor, + NULL); +} + +static void +clutter_box_layout_set_container (ClutterLayoutManager *layout, + ClutterContainer *container) +{ + ClutterBoxLayoutPrivate *priv = CLUTTER_BOX_LAYOUT (layout)->priv; + + priv->container = container; + + if (priv->container != NULL) + { + ClutterRequestMode request_mode; + + /* we need to change the :request-mode of the container + * to match the orientation + */ + request_mode = (priv->is_vertical) + ? CLUTTER_REQUEST_WIDTH_FOR_HEIGHT + : CLUTTER_REQUEST_HEIGHT_FOR_WIDTH; + clutter_actor_set_request_mode (CLUTTER_ACTOR (priv->container), + request_mode); + } +} + +static void +get_preferred_width (ClutterBoxLayout *self, + const GList *children, + gfloat for_height, + gfloat *min_width_p, + gfloat *natural_width_p) +{ + ClutterBoxLayoutPrivate *priv = self->priv; + gint n_children = 0; + const GList *l; + + if (min_width_p) + *min_width_p = 0; + + if (natural_width_p) + *natural_width_p = 0; + + for (l = children; l != NULL; l = l->next) + { + ClutterActor *child = l->data; + gfloat child_min = 0, child_nat = 0; + + if (!CLUTTER_ACTOR_IS_VISIBLE (child)) + continue; + + n_children++; + + clutter_actor_get_preferred_width (child, + (!priv->is_vertical) + ? for_height + : -1, + &child_min, + &child_nat); + + if (priv->is_vertical) + { + if (min_width_p) + *min_width_p = MAX (child_min, *min_width_p); + + if (natural_width_p) + *natural_width_p = MAX (child_nat, *natural_width_p); + } + else + { + if (min_width_p) + *min_width_p += child_min; + + if (natural_width_p) + *natural_width_p += child_nat; + } + } + + + if (!priv->is_vertical && n_children > 1) + { + if (min_width_p) + *min_width_p += priv->spacing * (n_children - 1); + + if (natural_width_p) + *natural_width_p += priv->spacing * (n_children - 1); + } +} + +static void +get_preferred_height (ClutterBoxLayout *self, + const GList *children, + gfloat for_height, + gfloat *min_height_p, + gfloat *natural_height_p) +{ + ClutterBoxLayoutPrivate *priv = self->priv; + gint n_children = 0; + const GList *l; + + if (min_height_p) + *min_height_p = 0; + + if (natural_height_p) + *natural_height_p = 0; + + for (l = children; l != NULL; l = l->next) + { + ClutterActor *child = l->data; + gfloat child_min = 0, child_nat = 0; + + if (!CLUTTER_ACTOR_IS_VISIBLE (child)) + continue; + + n_children++; + + clutter_actor_get_preferred_height (child, + (priv->is_vertical) + ? for_height + : -1, + &child_min, + &child_nat); + + if (!priv->is_vertical) + { + if (min_height_p) + *min_height_p = MAX (child_min, *min_height_p); + + if (natural_height_p) + *natural_height_p = MAX (child_nat, *natural_height_p); + } + else + { + if (min_height_p) + *min_height_p += child_min; + + if (natural_height_p) + *natural_height_p += child_nat; + } + } + + if (priv->is_vertical && n_children > 1) + { + if (min_height_p) + *min_height_p += priv->spacing * (n_children - 1); + + if (natural_height_p) + *natural_height_p += priv->spacing * (n_children - 1); + } +} + +static void +clutter_box_layout_get_preferred_width (ClutterLayoutManager *layout, + ClutterContainer *container, + gfloat for_height, + gfloat *min_width_p, + gfloat *natural_width_p) +{ + ClutterBoxLayout *self = CLUTTER_BOX_LAYOUT (layout); + GList *children; + + children = clutter_container_get_children (container); + + get_preferred_width (self, children, for_height, + min_width_p, + natural_width_p); + + g_list_free (children); +} + +static void +clutter_box_layout_get_preferred_height (ClutterLayoutManager *layout, + ClutterContainer *container, + gfloat for_width, + gfloat *min_height_p, + gfloat *natural_height_p) +{ + ClutterBoxLayout *self = CLUTTER_BOX_LAYOUT (layout); + GList *children; + + children = clutter_container_get_children (container); + + get_preferred_height (self, children, for_width, + min_height_p, + natural_height_p); + + g_list_free (children); +} + +static void +clutter_box_layout_allocate (ClutterLayoutManager *layout, + ClutterContainer *container, + const ClutterActorBox *box, + ClutterAllocationFlags flags) +{ + ClutterBoxLayoutPrivate *priv = CLUTTER_BOX_LAYOUT (layout)->priv; + gfloat avail_width, avail_height, pref_width, pref_height; + GList *children, *l; + gint n_expand_children, extra_space; + gfloat position; + + children = clutter_container_get_children (container); + if (children == NULL) + return; + + clutter_actor_box_get_size (box, &avail_width, &avail_height); + + if (priv->is_vertical) + { + get_preferred_height (CLUTTER_BOX_LAYOUT (layout), + children, avail_width, + NULL, + &pref_height); + + pref_width = avail_width; + } + else + { + get_preferred_width (CLUTTER_BOX_LAYOUT (layout), + children, avail_height, + NULL, + &pref_width); + + pref_height = avail_height; + } + + /* count the number of children with expand set to TRUE */ + n_expand_children = 0; + for (l = children; l; l = l->next) + { + ClutterLayoutMeta *meta; + + meta = clutter_layout_manager_get_child_meta (layout, + container, + l->data); + + if (CLUTTER_BOX_CHILD (meta)->expand) + n_expand_children++; + } + + if (n_expand_children == 0) + { + extra_space = 0; + n_expand_children = 1; + } + else + { + if (priv->is_vertical) + extra_space = (avail_height - pref_height) / n_expand_children; + else + extra_space = (avail_width - pref_width) / n_expand_children; + + /* don't shrink anything */ + if (extra_space < 0) + extra_space = 0; + } + + position = 0; + + for (l = (priv->is_pack_start) ? g_list_last (children) : children; + l != NULL; + l = (priv->is_pack_start) ? l->prev : l->next) + { + ClutterActor *child = l->data; + ClutterActorBox child_box; + ClutterBoxChild *box_child; + ClutterLayoutMeta *meta; + gfloat child_nat; + + if (!CLUTTER_ACTOR_IS_VISIBLE (child)) + continue; + + meta = clutter_layout_manager_get_child_meta (layout, + container, + child); + box_child = CLUTTER_BOX_CHILD (meta); + + if (priv->is_vertical) + { + clutter_actor_get_preferred_height (child, avail_width, + NULL, &child_nat); + + child_box.y1 = ceilf (position); + + if (box_child->expand) + child_box.y2 = ceilf (position + child_nat + extra_space); + else + child_box.y2 = ceilf (position + child_nat); + + child_box.x1 = 0; + child_box.x2 = ceilf (avail_width); + + allocate_fill (child, &child_box, box_child); + clutter_actor_allocate (child, &child_box, flags); + + if (box_child->expand) + position += (child_nat + priv->spacing + extra_space); + else + position += (child_nat + priv->spacing); + } + else + { + clutter_actor_get_preferred_width (child, avail_height, + NULL, &child_nat); + + child_box.x1 = ceilf (position); + + if (box_child->expand) + child_box.x2 = ceilf (position + child_nat + extra_space); + else + child_box.x2 = ceilf (position + child_nat); + + child_box.y1 = 0; + child_box.y2 = ceilf (avail_height); + + allocate_fill (child, &child_box, box_child); + clutter_actor_allocate (child, &child_box, flags); + + if (box_child->expand) + position += (child_nat + priv->spacing + extra_space); + else + position += (child_nat + priv->spacing); + } + } + + g_list_free (children); +} + +static void +clutter_box_layout_set_property (GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + ClutterBoxLayout *self = CLUTTER_BOX_LAYOUT (gobject); + + switch (prop_id) + { + case PROP_VERTICAL: + clutter_box_layout_set_vertical (self, g_value_get_boolean (value)); + break; + + case PROP_SPACING: + clutter_box_layout_set_spacing (self, g_value_get_uint (value)); + break; + + case PROP_PACK_START: + clutter_box_layout_set_pack_start (self, g_value_get_boolean (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +clutter_box_layout_get_property (GObject *gobject, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + ClutterBoxLayoutPrivate *priv = CLUTTER_BOX_LAYOUT (gobject)->priv; + + switch (prop_id) + { + case PROP_VERTICAL: + g_value_set_boolean (value, priv->is_vertical); + break; + + case PROP_SPACING: + g_value_set_uint (value, priv->spacing); + break; + + case PROP_PACK_START: + g_value_set_boolean (value, priv->is_pack_start); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +clutter_box_layout_class_init (ClutterBoxLayoutClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + ClutterLayoutManagerClass *layout_class; + GParamSpec *pspec; + + layout_class = CLUTTER_LAYOUT_MANAGER_CLASS (klass); + + gobject_class->set_property = clutter_box_layout_set_property; + gobject_class->get_property = clutter_box_layout_get_property; + + layout_class->get_preferred_width = + clutter_box_layout_get_preferred_width; + layout_class->get_preferred_height = + clutter_box_layout_get_preferred_height; + layout_class->allocate = clutter_box_layout_allocate; + layout_class->set_container = clutter_box_layout_set_container; + layout_class->create_child_meta = clutter_box_layout_create_child_meta; + + g_type_class_add_private (klass, sizeof (ClutterBoxLayoutPrivate)); + + /** + * ClutterBoxLayout:vertical: + * + * Whether the #ClutterBoxLayout should arrange its children + * alongside the Y axis, instead of alongside the X axis + * + * Since: 1.2 + */ + pspec = g_param_spec_boolean ("vertical", + "Vertical", + "Whether the layout should be vertical, rather" + "than horizontal", + FALSE, + CLUTTER_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_VERTICAL, pspec); + + /** + * ClutterBoxLayout:pack-start: + * + * Whether the #ClutterBoxLayout should pack items at the start + * or append them at the end + * + * Since: 1.2 + */ + pspec = g_param_spec_boolean ("pack-start", + "Pack Start", + "Whether to pack items at the start of the box", + FALSE, + CLUTTER_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_PACK_START, pspec); + + /** + * ClutterBoxLayout:spacing: + * + * The spacing between children of the #ClutterBoxLayout, in pixels + * + * Since: 1.2 + */ + pspec = g_param_spec_uint ("spacing", + "Spacing", + "Spacing between children", + 0, G_MAXUINT, 0, + CLUTTER_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_SPACING, pspec); +} + +static void +clutter_box_layout_init (ClutterBoxLayout *layout) +{ + ClutterBoxLayoutPrivate *priv; + + layout->priv = priv = CLUTTER_BOX_LAYOUT_GET_PRIVATE (layout); + + priv->is_vertical = FALSE; + priv->is_pack_start = FALSE; + priv->spacing = 0; +} + +/** + * clutter_box_layout_new: + * + * Creates a new #ClutterBoxLayout layout manager + * + * Return value: the newly created #ClutterBoxLayout + * + * Since: 1.2 + */ +ClutterLayoutManager * +clutter_box_layout_new (void) +{ + return g_object_new (CLUTTER_TYPE_BOX_LAYOUT, NULL); +} + +/** + * clutter_box_layout_set_spacing: + * @layout: a #ClutterBoxLayout + * @spacing: the spacing between children of the layout, in pixels + * + * Sets the spacing between children of @layout + * + * Since: 1.2 + */ +void +clutter_box_layout_set_spacing (ClutterBoxLayout *layout, + guint spacing) +{ + ClutterBoxLayoutPrivate *priv; + + g_return_if_fail (CLUTTER_IS_BOX_LAYOUT (layout)); + + priv = layout->priv; + + if (priv->spacing != spacing) + { + ClutterLayoutManager *manager; + + priv->spacing = spacing; + + manager = CLUTTER_LAYOUT_MANAGER (layout); + clutter_layout_manager_layout_changed (manager); + + g_object_notify (G_OBJECT (layout), "spacing"); + } +} + +/** + * clutter_box_layout_get_spacing: + * @layout: a #ClutterBoxLayout + * + * Retrieves the spacing set using clutter_box_layout_set_spacing() + * + * Return value: the spacing between children of the #ClutterBoxLayout + * + * Since: 1.2 + */ +guint +clutter_box_layout_get_spacing (ClutterBoxLayout *layout) +{ + g_return_val_if_fail (CLUTTER_IS_BOX_LAYOUT (layout), 0); + + return layout->priv->spacing; +} + +/** + * clutter_box_layout_set_vertical: + * @layout: a #ClutterBoxLayout + * @vertical: %TRUE if the layout should be vertical + * + * Sets whether @layout should arrange its children vertically alongside + * the Y axis, instead of horizontally alongside the X axis + * + * Since: 1.2 + */ +void +clutter_box_layout_set_vertical (ClutterBoxLayout *layout, + gboolean vertical) +{ + ClutterBoxLayoutPrivate *priv; + + g_return_if_fail (CLUTTER_IS_BOX_LAYOUT (layout)); + + priv = layout->priv; + + if (priv->is_vertical != vertical) + { + ClutterLayoutManager *manager; + + priv->is_vertical = vertical ? TRUE : FALSE; + + manager = CLUTTER_LAYOUT_MANAGER (layout); + clutter_layout_manager_layout_changed (manager); + + g_object_notify (G_OBJECT (layout), "vertical"); + } +} + +/** + * clutter_box_layout_get_vertical: + * @layout: a #ClutterBoxLayout + * + * Retrieves the orientation of the @layout as set using the + * clutter_box_layout_set_vertical() function + * + * Return value: %TRUE if the #ClutterBoxLayout is arranging its children + * vertically, and %FALSE otherwise + * + * Since: 1.2 + */ +gboolean +clutter_box_layout_get_vertical (ClutterBoxLayout *layout) +{ + g_return_val_if_fail (CLUTTER_IS_BOX_LAYOUT (layout), FALSE); + + return layout->priv->is_vertical; +} + +/** + * clutter_box_layout_set_pack_start: + * @layout: a #ClutterBoxLayout + * @pack_start: %TRUE if the @layout should pack children at the + * beginning of the layout + * + * Sets whether children of @layout should be layed out by appending + * them or by prepending them + * + * Since: 1.2 + */ +void +clutter_box_layout_set_pack_start (ClutterBoxLayout *layout, + gboolean pack_start) +{ + ClutterBoxLayoutPrivate *priv; + + g_return_if_fail (CLUTTER_IS_BOX_LAYOUT (layout)); + + priv = layout->priv; + + if (priv->is_pack_start != pack_start) + { + ClutterLayoutManager *manager; + + priv->is_pack_start = pack_start ? TRUE : FALSE; + + manager = CLUTTER_LAYOUT_MANAGER (layout); + clutter_layout_manager_layout_changed (manager); + + g_object_notify (G_OBJECT (layout), "pack-start"); + } +} + +/** + * clutter_box_layout_get_pack_start: + * @layout: a #ClutterBoxLayout + * + * Retrieves the value set using clutter_box_layout_set_pack_start() + * + * Return value: %TRUE if the #ClutterBoxLayout should pack children + * at the beginning of the layout, and %FALSE otherwise + * + * Since: 1.2 + */ +gboolean +clutter_box_layout_get_pack_start (ClutterBoxLayout *layout) +{ + g_return_val_if_fail (CLUTTER_IS_BOX_LAYOUT (layout), FALSE); + + return layout->priv->is_pack_start; +} + +/** + * clutter_box_layout_pack: + * @layout: a #ClutterBoxLayout + * @actor: a #ClutterActor + * @expand: whether the @actor should expand + * @x_fill: whether the @actor should fill horizontally + * @y_fill: whether the @actor should fill vertically + * @x_align: the horizontal alignment policy for @actor + * @y_align: the vertical alignment policy for @actor + * + * Packs @actor inside the #ClutterContainer associated to @layout + * and sets the layout properties + * + * Since: 1.2 + */ +void +clutter_box_layout_pack (ClutterBoxLayout *layout, + ClutterActor *actor, + gboolean expand, + gboolean x_fill, + gboolean y_fill, + ClutterBoxAlignment x_align, + ClutterBoxAlignment y_align) +{ + ClutterBoxLayoutPrivate *priv; + ClutterLayoutManager *manager; + ClutterLayoutMeta *meta; + + g_return_if_fail (CLUTTER_IS_BOX_LAYOUT (layout)); + g_return_if_fail (CLUTTER_IS_ACTOR (actor)); + + priv = layout->priv; + + if (priv->container == NULL) + { + g_warning ("The layout of type '%s' must be associated to " + "a ClutterContainer before adding children", + G_OBJECT_TYPE_NAME (layout)); + return; + } + + clutter_container_add_actor (priv->container, actor); + + manager = CLUTTER_LAYOUT_MANAGER (layout); + meta = clutter_layout_manager_get_child_meta (manager, + priv->container, + actor); + g_assert (CLUTTER_IS_BOX_CHILD (meta)); + + box_child_set_align (CLUTTER_BOX_CHILD (meta), x_align, y_align); + box_child_set_fill (CLUTTER_BOX_CHILD (meta), x_fill, y_fill); + box_child_set_expand (CLUTTER_BOX_CHILD (meta), expand); +} + +/** + * clutter_box_layout_set_alignment: + * @layout: a #ClutterBoxLayout + * @actor: a #ClutterActor child of @layout + * @x_align: Horizontal alignment policy for @actor + * @y_align: Vertical alignment policy for @actor + * + * Sets the horizontal and vertical alignment policies for @actor + * inside @layout + * + * Since: 1.2 + */ +void +clutter_box_layout_set_alignment (ClutterBoxLayout *layout, + ClutterActor *actor, + ClutterBoxAlignment x_align, + ClutterBoxAlignment y_align) +{ + ClutterBoxLayoutPrivate *priv; + ClutterLayoutManager *manager; + ClutterLayoutMeta *meta; + + g_return_if_fail (CLUTTER_IS_BOX_LAYOUT (layout)); + g_return_if_fail (CLUTTER_IS_ACTOR (actor)); + + priv = layout->priv; + + if (priv->container == NULL) + { + g_warning ("The layout of type '%s' must be associated to " + "a ClutterContainer before querying layout " + "properties", + G_OBJECT_TYPE_NAME (layout)); + return; + } + + manager = CLUTTER_LAYOUT_MANAGER (layout); + meta = clutter_layout_manager_get_child_meta (manager, + priv->container, + actor); + if (meta == NULL) + { + g_warning ("No layout meta found for the child of type '%s' " + "inside the layout manager of type '%s'", + G_OBJECT_TYPE_NAME (actor), + G_OBJECT_TYPE_NAME (manager)); + return; + } + + g_assert (CLUTTER_IS_BOX_CHILD (meta)); + + box_child_set_align (CLUTTER_BOX_CHILD (meta), x_align, y_align); +} + +/** + * clutter_box_layout_get_alignment: + * @layout: a #ClutterBoxLayout + * @actor: a #ClutterActor child of @layout + * @x_align: (out): return location for the horizontal alignment policy + * @y_align: (out): return location for the vertical alignment policy + * + * Retrieves the horizontal and vertical alignment policies for @actor + * as set using clutter_box_layout_pack() or clutter_box_layout_set_alignment() + * + * Since: 1.2 + */ +void +clutter_box_layout_get_alignment (ClutterBoxLayout *layout, + ClutterActor *actor, + ClutterBoxAlignment *x_align, + ClutterBoxAlignment *y_align) +{ + ClutterBoxLayoutPrivate *priv; + ClutterLayoutManager *manager; + ClutterLayoutMeta *meta; + + g_return_if_fail (CLUTTER_IS_BOX_LAYOUT (layout)); + g_return_if_fail (CLUTTER_IS_ACTOR (actor)); + + priv = layout->priv; + + if (priv->container == NULL) + { + g_warning ("The layout of type '%s' must be associated to " + "a ClutterContainer before querying layout " + "properties", + G_OBJECT_TYPE_NAME (layout)); + return; + } + + manager = CLUTTER_LAYOUT_MANAGER (layout); + meta = clutter_layout_manager_get_child_meta (manager, + priv->container, + actor); + if (meta == NULL) + { + g_warning ("No layout meta found for the child of type '%s' " + "inside the layout manager of type '%s'", + G_OBJECT_TYPE_NAME (actor), + G_OBJECT_TYPE_NAME (manager)); + return; + } + + g_assert (CLUTTER_IS_BOX_CHILD (meta)); + + if (x_align) + *x_align = CLUTTER_BOX_CHILD (meta)->x_align; + + if (y_align) + *y_align = CLUTTER_BOX_CHILD (meta)->y_align; +} + +/** + * clutter_box_layout_set_fill: + * @layout: a #ClutterBoxLayout + * @actor: a #ClutterActor child of @layout + * @x_fill: whether @actor should fill horizontally the allocated space + * @y_fill: whether @actor should fill vertically the allocated space + * + * Sets the horizontal and vertical fill policies for @actor + * inside @layout + * + * Since: 1.2 + */ +void +clutter_box_layout_set_fill (ClutterBoxLayout *layout, + ClutterActor *actor, + gboolean x_fill, + gboolean y_fill) +{ + ClutterBoxLayoutPrivate *priv; + ClutterLayoutManager *manager; + ClutterLayoutMeta *meta; + + g_return_if_fail (CLUTTER_IS_BOX_LAYOUT (layout)); + g_return_if_fail (CLUTTER_IS_ACTOR (actor)); + + priv = layout->priv; + + if (priv->container == NULL) + { + g_warning ("The layout of type '%s' must be associated to " + "a ClutterContainer before querying layout " + "properties", + G_OBJECT_TYPE_NAME (layout)); + return; + } + + manager = CLUTTER_LAYOUT_MANAGER (layout); + meta = clutter_layout_manager_get_child_meta (manager, + priv->container, + actor); + if (meta == NULL) + { + g_warning ("No layout meta found for the child of type '%s' " + "inside the layout manager of type '%s'", + G_OBJECT_TYPE_NAME (actor), + G_OBJECT_TYPE_NAME (manager)); + return; + } + + g_assert (CLUTTER_IS_BOX_CHILD (meta)); + + box_child_set_fill (CLUTTER_BOX_CHILD (meta), x_fill, y_fill); +} + +/** + * clutter_box_layout_get_fill: + * @layout: a #ClutterBoxLayout + * @actor: a #ClutterActor child of @layout + * @x_fill: (out): return location for the horizontal fill policy + * @y_fill: (out): return location for the vertical fill policy + * + * Retrieves the horizontal and vertical fill policies for @actor + * as set using clutter_box_layout_pack() or clutter_box_layout_set_fill() + * + * Since: 1.2 + */ +void +clutter_box_layout_get_fill (ClutterBoxLayout *layout, + ClutterActor *actor, + gboolean *x_fill, + gboolean *y_fill) +{ + ClutterBoxLayoutPrivate *priv; + ClutterLayoutManager *manager; + ClutterLayoutMeta *meta; + + g_return_if_fail (CLUTTER_IS_BOX_LAYOUT (layout)); + g_return_if_fail (CLUTTER_IS_ACTOR (actor)); + + priv = layout->priv; + + if (priv->container == NULL) + { + g_warning ("The layout of type '%s' must be associated to " + "a ClutterContainer before querying layout " + "properties", + G_OBJECT_TYPE_NAME (layout)); + return; + } + + manager = CLUTTER_LAYOUT_MANAGER (layout); + meta = clutter_layout_manager_get_child_meta (manager, + priv->container, + actor); + if (meta == NULL) + { + g_warning ("No layout meta found for the child of type '%s' " + "inside the layout manager of type '%s'", + G_OBJECT_TYPE_NAME (actor), + G_OBJECT_TYPE_NAME (manager)); + return; + } + + g_assert (CLUTTER_IS_BOX_CHILD (meta)); + + if (x_fill) + *x_fill = CLUTTER_BOX_CHILD (meta)->x_fill; + + if (y_fill) + *y_fill = CLUTTER_BOX_CHILD (meta)->y_fill; +} + +/** + * clutter_box_layout_set_expand: + * @layout: a #ClutterBoxLayout + * @actor: a #ClutterActor child of @layout + * @expand: whether @actor should expand + * + * Sets whether @actor should expand inside @layout + * + * Since: 1.2 + */ +void +clutter_box_layout_set_expand (ClutterBoxLayout *layout, + ClutterActor *actor, + gboolean expand) +{ + ClutterBoxLayoutPrivate *priv; + ClutterLayoutManager *manager; + ClutterLayoutMeta *meta; + + g_return_if_fail (CLUTTER_IS_BOX_LAYOUT (layout)); + g_return_if_fail (CLUTTER_IS_ACTOR (actor)); + + priv = layout->priv; + + if (priv->container == NULL) + { + g_warning ("The layout of type '%s' must be associated to " + "a ClutterContainer before querying layout " + "properties", + G_OBJECT_TYPE_NAME (layout)); + return; + } + + manager = CLUTTER_LAYOUT_MANAGER (layout); + meta = clutter_layout_manager_get_child_meta (manager, + priv->container, + actor); + if (meta == NULL) + { + g_warning ("No layout meta found for the child of type '%s' " + "inside the layout manager of type '%s'", + G_OBJECT_TYPE_NAME (actor), + G_OBJECT_TYPE_NAME (manager)); + return; + } + + g_assert (CLUTTER_IS_BOX_CHILD (meta)); + + box_child_set_expand (CLUTTER_BOX_CHILD (meta), expand); +} + +/** + * clutter_box_layout_get_expand: + * @layout: a #ClutterBoxLayout + * @actor: a #ClutterActor child of @layout + * + * Retrieves whether @actor should expand inside @layout + * + * Return value: %TRUE if the #ClutterActor should expand, %FALSE otherwise + * + * Since: 1.2 + */ +gboolean +clutter_box_layout_get_expand (ClutterBoxLayout *layout, + ClutterActor *actor) +{ + ClutterBoxLayoutPrivate *priv; + ClutterLayoutManager *manager; + ClutterLayoutMeta *meta; + + g_return_val_if_fail (CLUTTER_IS_BOX_LAYOUT (layout), FALSE); + g_return_val_if_fail (CLUTTER_IS_ACTOR (actor), FALSE); + + priv = layout->priv; + + if (priv->container == NULL) + { + g_warning ("The layout of type '%s' must be associated to " + "a ClutterContainer before querying layout " + "properties", + G_OBJECT_TYPE_NAME (layout)); + return FALSE; + } + + manager = CLUTTER_LAYOUT_MANAGER (layout); + meta = clutter_layout_manager_get_child_meta (manager, + priv->container, + actor); + if (meta == NULL) + { + g_warning ("No layout meta found for the child of type '%s' " + "inside the layout manager of type '%s'", + G_OBJECT_TYPE_NAME (actor), + G_OBJECT_TYPE_NAME (manager)); + return FALSE; + } + + g_assert (CLUTTER_IS_BOX_CHILD (meta)); + + return CLUTTER_BOX_CHILD (meta)->expand; +} diff --git a/clutter/clutter-box-layout.h b/clutter/clutter-box-layout.h new file mode 100644 index 000000000..be2be9130 --- /dev/null +++ b/clutter/clutter-box-layout.h @@ -0,0 +1,143 @@ +/* + * Clutter. + * + * An OpenGL based 'interactive canvas' library. + * + * Copyright (C) 2009 Intel Corporation. + * + * 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 . + * + * Author: + * Emmanuele Bassi + * + * Based on the NBTK NbtkBoxLayout actor by: + * Thomas Wood + */ + +#if !defined(__CLUTTER_H_INSIDE__) && !defined(CLUTTER_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __CLUTTER_BOX_LAYOUT_H__ +#define __CLUTTER_BOX_LAYOUT_H__ + +#include + +G_BEGIN_DECLS + +#define CLUTTER_TYPE_BOX_LAYOUT (clutter_box_layout_get_type ()) +#define CLUTTER_BOX_LAYOUT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CLUTTER_TYPE_BOX_LAYOUT, ClutterBoxLayout)) +#define CLUTTER_IS_BOX_LAYOUT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CLUTTER_TYPE_BOX_LAYOUT)) +#define CLUTTER_BOX_LAYOUT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CLUTTER_TYPE_BOX_LAYOUT, ClutterBoxLayoutClass)) +#define CLUTTER_IS_BOX_LAYOUT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CLUTTER_TYPE_BOX_LAYOUT)) +#define CLUTTER_BOX_LAYOUT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CLUTTER_TYPE_BOX_LAYOUT, ClutterBoxLayoutClass)) + +typedef struct _ClutterBoxLayout ClutterBoxLayout; +typedef struct _ClutterBoxLayoutPrivate ClutterBoxLayoutPrivate; +typedef struct _ClutterBoxLayoutClass ClutterBoxLayoutClass; + +/** + * ClutterBoxAlignment: + * @CLUTTER_BOX_ALIGNMENT_START: Align the child to the top or to + * to the left, depending on the used axis + * @CLUTTER_BOX_ALIGNMENT_CENTER: Align the child to the center + * @CLUTTER_BOX_ALIGNMENT_END: Align the child to the bottom or to + * the right, depending on the used axis + * + * The alignment policies available on each axis of the #ClutterBoxLayout + * + * Since: 1.2 + */ +typedef enum { + CLUTTER_BOX_ALIGNMENT_START, + CLUTTER_BOX_ALIGNMENT_END, + CLUTTER_BOX_ALIGNMENT_CENTER +} ClutterBoxAlignment; + +/** + * ClutterBoxLayout: + * + * The #ClutterBoxLayout structure contains only private data + * and should be accessed using the provided API + * + * Since: 1.2 + */ +struct _ClutterBoxLayout +{ + /*< private >*/ + ClutterLayoutManager parent_instance; + + ClutterBoxLayoutPrivate *priv; +}; + +/** + * ClutterBoxLayoutClass: + * + * The #ClutterBoxLayoutClass structure contains only private + * data and should be accessed using the provided API + * + * Since: 1.2 + */ +struct _ClutterBoxLayoutClass +{ + /*< private >*/ + ClutterLayoutManagerClass parent_class; +}; + +GType clutter_box_layout_get_type (void) G_GNUC_CONST; + +ClutterLayoutManager *clutter_box_layout_new (void); + +void clutter_box_layout_set_spacing (ClutterBoxLayout *layout, + guint spacing); +guint clutter_box_layout_get_spacing (ClutterBoxLayout *layout); +void clutter_box_layout_set_vertical (ClutterBoxLayout *layout, + gboolean vertical); +gboolean clutter_box_layout_get_vertical (ClutterBoxLayout *layout); +void clutter_box_layout_set_pack_start (ClutterBoxLayout *layout, + gboolean pack_start); +gboolean clutter_box_layout_get_pack_start (ClutterBoxLayout *layout); + +void clutter_box_layout_pack (ClutterBoxLayout *layout, + ClutterActor *actor, + gboolean expand, + gboolean x_fill, + gboolean y_fill, + ClutterBoxAlignment x_align, + ClutterBoxAlignment y_align); +void clutter_box_layout_set_alignment (ClutterBoxLayout *layout, + ClutterActor *actor, + ClutterBoxAlignment x_align, + ClutterBoxAlignment y_align); +void clutter_box_layout_get_alignment (ClutterBoxLayout *layout, + ClutterActor *actor, + ClutterBoxAlignment *x_align, + ClutterBoxAlignment *y_align); +void clutter_box_layout_set_fill (ClutterBoxLayout *layout, + ClutterActor *actor, + gboolean x_fill, + gboolean y_fill); +void clutter_box_layout_get_fill (ClutterBoxLayout *layout, + ClutterActor *actor, + gboolean *x_fill, + gboolean *y_fill); +void clutter_box_layout_set_expand (ClutterBoxLayout *layout, + ClutterActor *actor, + gboolean expand); +gboolean clutter_box_layout_get_expand (ClutterBoxLayout *layout, + ClutterActor *actor); + +G_END_DECLS + +#endif /* __CLUTTER_BOX_LAYOUT_H__ */ diff --git a/clutter/clutter-box.c b/clutter/clutter-box.c new file mode 100644 index 000000000..131327551 --- /dev/null +++ b/clutter/clutter-box.c @@ -0,0 +1,828 @@ +/** + * SECTION:clutter-box + * @short_description: A Generic layout container + * + * #ClutterBox is a #ClutterActor sub-class implementing the #ClutterContainer + * interface. A Box delegates the whole size requisition and size allocation to + * a #ClutterLayoutManager instance. + * + * + * Using ClutterBox + * The following code shows how to create a #ClutterBox with + * a #ClutterLayoutManager sub-class, and how to add children to + * it via clutter_box_pack(). + * + * ClutterActor *box; + * ClutterLayoutManager *layout; + * + * /* Create the layout manager first */ + * layout = clutter_box_layout_new (); + * clutter_box_layout_set_homogeneous (CLUTTER_BOX_LAYOUT (layout), TRUE); + * clutter_box_layout_set_spacing (CLUTTER_BOX_LAYOUT (layout), 12); + * + * /* Then create the ClutterBox actor. The Box will take + * * ownership of the ClutterLayoutManager instance by sinking + * * its floating reference + * */ + * box = clutter_box_new (layout); + * + * /* Now add children to the Box using the variadic arguments + * * function clutter_box_pack() to set layout properties + * */ + * clutter_box_pack (CLUTTER_BOX (box), actor, + * "x-align", CLUTTER_BOX_ALIGNMENT_CENTER, + * "y-align", CLUTTER_BOX_ALIGNMENT_END, + * "expand", TRUE, + * NULL); + * + * + * + * #ClutterBox's clutter_box_pack() wraps the generic + * clutter_container_add_actor() function, but it also allows setting + * layout properties while adding the new child to the box. + * + * #ClutterBox is available since Clutter 1.2 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "clutter-box.h" +#include "clutter-debug.h" +#include "clutter-enum-types.h" +#include "clutter-marshal.h" +#include "clutter-private.h" + +#define CLUTTER_BOX_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLUTTER_TYPE_BOX, ClutterBoxPrivate)) + +struct _ClutterBoxPrivate +{ + ClutterLayoutManager *manager; + + GList *children; + + guint changed_id; + + ClutterColor color; + guint color_set : 1; +}; + +enum +{ + PROP_0, + + PROP_LAYOUT_MANAGER, + PROP_COLOR, + PROP_COLOR_SET +}; + +static const ClutterColor default_box_color = { 255, 255, 255, 255 }; + +static void clutter_container_iface_init (ClutterContainerIface *iface); + +G_DEFINE_TYPE_WITH_CODE (ClutterBox, clutter_box, 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 ((ClutterActor *) a); + gfloat depth_b = clutter_actor_get_depth ((ClutterActor *) b); + + if (depth_a < depth_b) + return -1; + + if (depth_a > depth_b) + return 1; + + return 0; +} + +static void +clutter_box_real_add (ClutterContainer *container, + ClutterActor *actor) +{ + ClutterBoxPrivate *priv = CLUTTER_BOX (container)->priv; + + g_object_ref (actor); + + priv->children = g_list_insert_sorted (priv->children, + actor, + sort_by_depth); + + clutter_actor_set_parent (actor, CLUTTER_ACTOR (container)); + + clutter_layout_manager_add_child_meta (priv->manager, + container, + actor); + + clutter_actor_queue_relayout (actor); + + g_signal_emit_by_name (container, "actor-added", actor); + + g_object_unref (actor); +} + +static void +clutter_box_real_remove (ClutterContainer *container, + ClutterActor *actor) +{ + ClutterBoxPrivate *priv = CLUTTER_BOX (container)->priv; + + g_object_ref (actor); + + priv->children = g_list_remove (priv->children, actor); + clutter_actor_unparent (actor); + + clutter_layout_manager_remove_child_meta (priv->manager, + container, + actor); + + clutter_actor_queue_relayout (CLUTTER_ACTOR (container)); + + g_signal_emit_by_name (container, "actor-removed", actor); + + g_object_unref (actor); +} + +static void +clutter_box_real_foreach (ClutterContainer *container, + ClutterCallback callback, + gpointer user_data) +{ + ClutterBoxPrivate *priv = CLUTTER_BOX (container)->priv; + GList *l; + + for (l = priv->children; l != NULL; l = l->next) + (* callback) (l->data, user_data); +} + +static void +clutter_box_real_raise (ClutterContainer *container, + ClutterActor *actor, + ClutterActor *sibling) +{ + ClutterBoxPrivate *priv = CLUTTER_BOX (container)->priv; + + priv->children = g_list_remove (priv->children, actor); + + if (sibling == NULL) + 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_); + } + + clutter_actor_queue_redraw (CLUTTER_ACTOR (container)); +} + +static void +clutter_box_real_lower (ClutterContainer *container, + ClutterActor *actor, + ClutterActor *sibling) +{ + ClutterBoxPrivate *priv = CLUTTER_BOX (container)->priv; + + priv->children = g_list_remove (priv->children, actor); + + if (sibling == NULL) + 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_); + } + + clutter_actor_queue_redraw (CLUTTER_ACTOR (container)); +} + +static void +clutter_box_real_sort_depth_order (ClutterContainer *container) +{ + ClutterBoxPrivate *priv = CLUTTER_BOX (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_box_real_add; + iface->remove = clutter_box_real_remove; + iface->foreach = clutter_box_real_foreach; + iface->raise = clutter_box_real_raise; + iface->lower = clutter_box_real_lower; + iface->sort_depth_order = clutter_box_real_sort_depth_order; +} + +static void +clutter_box_real_paint (ClutterActor *actor) +{ + ClutterBoxPrivate *priv = CLUTTER_BOX (actor)->priv; + + if (priv->color_set) + { + ClutterActorBox box = { 0, }; + gfloat width, height; + guint8 tmp_alpha; + + clutter_actor_get_allocation_box (actor, &box); + clutter_actor_box_get_size (&box, &width, &height); + + tmp_alpha = clutter_actor_get_paint_opacity (actor) + * priv->color.alpha + / 255; + + cogl_set_source_color4ub (priv->color.red, + priv->color.green, + priv->color.blue, + tmp_alpha); + + cogl_rectangle (0, 0, width, height); + } + + g_list_foreach (priv->children, (GFunc) clutter_actor_paint, NULL); +} + +static void +clutter_box_real_pick (ClutterActor *actor, + const ClutterColor *pick) +{ + ClutterBoxPrivate *priv = CLUTTER_BOX (actor)->priv; + + CLUTTER_ACTOR_CLASS (clutter_box_parent_class)->pick (actor, pick); + + g_list_foreach (priv->children, (GFunc) clutter_actor_paint, NULL); +} + +static void +clutter_box_real_get_preferred_width (ClutterActor *actor, + gfloat for_height, + gfloat *min_width, + gfloat *natural_width) +{ + ClutterBoxPrivate *priv = CLUTTER_BOX (actor)->priv; + + /* if we don't have any children don't bother proxying the + * call to the layout manager instance + */ + if (priv->children == NULL) + { + if (min_width) + *min_width = 0.0; + + if (natural_width) + *natural_width = 0.0; + + return; + } + + clutter_layout_manager_get_preferred_width (priv->manager, + CLUTTER_CONTAINER (actor), + for_height, + min_width, natural_width); +} + +static void +clutter_box_real_get_preferred_height (ClutterActor *actor, + gfloat for_width, + gfloat *min_height, + gfloat *natural_height) +{ + ClutterBoxPrivate *priv = CLUTTER_BOX (actor)->priv; + + /* if we don't have any children don't bother proxying the + * call to the layout manager instance + */ + if (priv->children == NULL) + { + if (min_height) + *min_height = 0.0; + + if (natural_height) + *natural_height = 0.0; + + return; + } + + clutter_layout_manager_get_preferred_height (priv->manager, + CLUTTER_CONTAINER (actor), + for_width, + min_height, natural_height); +} + +static void +clutter_box_real_allocate (ClutterActor *actor, + const ClutterActorBox *allocation, + ClutterAllocationFlags flags) +{ + ClutterBoxPrivate *priv = CLUTTER_BOX (actor)->priv; + ClutterActorClass *klass; + + klass = CLUTTER_ACTOR_CLASS (clutter_box_parent_class); + klass->allocate (actor, allocation, flags); + + if (priv->children == NULL) + return; + + clutter_layout_manager_allocate (priv->manager, + CLUTTER_CONTAINER (actor), + allocation, flags); +} + +static void +clutter_box_destroy (ClutterActor *actor) +{ + ClutterBoxPrivate *priv = CLUTTER_BOX (actor)->priv; + + /* destroy all our children */ + g_list_foreach (priv->children, (GFunc) clutter_actor_destroy, NULL); + + CLUTTER_ACTOR_CLASS (clutter_box_parent_class)->destroy (actor); +} + +static inline void +set_layout_manager (ClutterBox *self, + ClutterLayoutManager *manager) +{ + ClutterBoxPrivate *priv = self->priv; + + if (priv->manager == manager) + return; + + if (priv->manager != NULL) + { + if (priv->changed_id != 0) + g_signal_handler_disconnect (priv->manager, priv->changed_id); + + clutter_layout_manager_set_container (priv->manager, NULL); + g_object_unref (priv->manager); + + priv->manager = NULL; + priv->changed_id = 0; + } + + if (manager != NULL) + { + priv->manager = g_object_ref_sink (manager); + clutter_layout_manager_set_container (manager, + CLUTTER_CONTAINER (self)); + + priv->changed_id = + g_signal_connect_swapped (priv->manager, "layout-changed", + G_CALLBACK (clutter_actor_queue_relayout), + self); + } + + clutter_actor_queue_relayout (CLUTTER_ACTOR (self)); + + g_object_notify (G_OBJECT (self), "layout-manager"); +} + +static void +clutter_box_dispose (GObject *gobject) +{ + ClutterBox *self = CLUTTER_BOX (gobject); + + set_layout_manager (self, NULL); + + G_OBJECT_CLASS (clutter_box_parent_class)->dispose (gobject); +} + +static void +clutter_box_set_property (GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + ClutterBox *self = CLUTTER_BOX (gobject); + + switch (prop_id) + { + case PROP_LAYOUT_MANAGER: + set_layout_manager (self, g_value_get_object (value)); + break; + + case PROP_COLOR: + clutter_box_set_color (self, clutter_value_get_color (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +clutter_box_get_property (GObject *gobject, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + ClutterBoxPrivate *priv = CLUTTER_BOX (gobject)->priv; + + switch (prop_id) + { + case PROP_LAYOUT_MANAGER: + g_value_set_object (value, priv->manager); + break; + + case PROP_COLOR: + clutter_value_set_color (value, &priv->color); + break; + + case PROP_COLOR_SET: + g_value_set_boolean (value, priv->color_set); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +clutter_box_class_init (ClutterBoxClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); + GParamSpec *pspec; + + g_type_class_add_private (klass, sizeof (ClutterBoxPrivate)); + + actor_class->get_preferred_width = clutter_box_real_get_preferred_width; + actor_class->get_preferred_height = clutter_box_real_get_preferred_height; + actor_class->allocate = clutter_box_real_allocate; + actor_class->paint = clutter_box_real_paint; + actor_class->pick = clutter_box_real_pick; + actor_class->destroy = clutter_box_destroy; + + gobject_class->set_property = clutter_box_set_property; + gobject_class->get_property = clutter_box_get_property; + gobject_class->dispose = clutter_box_dispose; + + /** + * ClutterBox:layout-manager: + * + * The #ClutterLayoutManager used by the #ClutterBox + * + * Since: 1.2 + */ + pspec = g_param_spec_object ("layout-manager", + "Layout Manager", + "The layout manager used by the box", + CLUTTER_TYPE_LAYOUT_MANAGER, + CLUTTER_PARAM_READWRITE | + G_PARAM_CONSTRUCT); + g_object_class_install_property (gobject_class, + PROP_LAYOUT_MANAGER, + pspec); + + /** + * ClutterBox:color: + * + * The color to be used to paint the background of the + * #ClutterBox. Setting this property will set the + * #ClutterBox:color-set property as a side effect + * + * Since: 1.2 + */ + pspec = clutter_param_spec_color ("color", + "Color", + "The background color of the box", + &default_box_color, + CLUTTER_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_COLOR, pspec); + + /** + * ClutterBox:color-set: + * + * Whether the #ClutterBox:color property has been set + * + * Since: 1.2 + */ + pspec = g_param_spec_boolean ("color-set", + "Color Set", + "Whether the background color is set", + FALSE, + CLUTTER_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_COLOR_SET, pspec); +} + +static void +clutter_box_init (ClutterBox *self) +{ + self->priv = CLUTTER_BOX_GET_PRIVATE (self); + + self->priv->color = default_box_color; +} + +/** + * clutter_box_new: + * @manager: a #ClutterLayoutManager + * + * Creates a new #ClutterBox. The children of the box will be layed + * out by the passed @manager + * + * Return value: the newly created #ClutterBox actor + * + * Since: 1.0 + */ +ClutterActor * +clutter_box_new (ClutterLayoutManager *manager) +{ + g_return_val_if_fail (CLUTTER_IS_LAYOUT_MANAGER (manager), NULL); + + return g_object_new (CLUTTER_TYPE_BOX, + "layout-manager", manager, + NULL); +} + +/** + * clutter_box_set_layout_manager: + * @box: a #ClutterBox + * @manager: a #ClutterLayoutManager + * + * Sets the #ClutterLayoutManager for @box + * + * A #ClutterLayoutManager is a delegate object that controls the + * layout of the children of @box + * + * Since: 1.2 + */ +void +clutter_box_set_layout_manager (ClutterBox *box, + ClutterLayoutManager *manager) +{ + g_return_if_fail (CLUTTER_IS_BOX (box)); + g_return_if_fail (CLUTTER_IS_LAYOUT_MANAGER (manager)); + + set_layout_manager (box, manager); +} + +/** + * clutter_box_get_layout_manager: + * @box: a #ClutterBox + * + * Retrieves the #ClutterLayoutManager instance used by @box + * + * Return value: a #ClutterLayoutManager + * + * Since: 1.2 + */ +ClutterLayoutManager * +clutter_box_get_layout_manager (ClutterBox *box) +{ + g_return_val_if_fail (CLUTTER_IS_BOX (box), NULL); + + return box->priv->manager; +} + +/** + * clutter_box_packv: + * @box: a #ClutterBox + * @actor: a #ClutterActor + * @n_properties: the number of properties to set + * @properties: (array length=n_properties) (element-type utf8): a vector + * containing the property names to set + * @values: (array length=n_properties): a vector containing the property + * values to set + * + * Vector-based variant of clutter_box_pack(), intended for language + * bindings to use + * + * Since: 1.2 + */ +void +clutter_box_packv (ClutterBox *box, + ClutterActor *actor, + guint n_properties, + const gchar * const properties[], + const GValue *values) +{ + ClutterContainer *container; + ClutterBoxPrivate *priv; + ClutterLayoutMeta *meta; + GObjectClass *klass; + gint i; + + g_return_if_fail (CLUTTER_IS_BOX (box)); + g_return_if_fail (CLUTTER_IS_ACTOR (actor)); + + container = CLUTTER_CONTAINER (box); + clutter_container_add_actor (container, actor); + + priv = box->priv; + + meta = clutter_layout_manager_get_child_meta (priv->manager, + container, + actor); + + if (meta == NULL) + return; + + klass = G_OBJECT_GET_CLASS (meta); + + for (i = 0; i < n_properties; i++) + { + const gchar *pname = properties[i]; + GParamSpec *pspec; + + pspec = g_object_class_find_property (klass, pname); + if (pspec == NULL) + { + g_warning ("%s: the layout property '%s' for managers " + "of type '%s' (meta type '%s') does not exist", + G_STRLOC, + pname, + G_OBJECT_TYPE_NAME (priv->manager), + G_OBJECT_TYPE_NAME (meta)); + break; + } + + if (!(pspec->flags & G_PARAM_WRITABLE)) + { + g_warning ("%s: the layout property '%s' for managers " + "of type '%s' (meta type '%s') is not writable", + G_STRLOC, + pspec->name, + G_OBJECT_TYPE_NAME (priv->manager), + G_OBJECT_TYPE_NAME (meta)); + break; + } + + clutter_layout_manager_child_set_property (priv->manager, + container, actor, + pname, &values[i]); + } +} + +/** + * clutter_box_pack: + * @box: a #ClutterBox + * @actor: a #ClutterActor + * @first_property: the name of the first property to set, or %NULL + * @Varargs: a list of property name and value pairs, terminated by %NULL + * + * Adds @actor to @box and sets layout properties at the same time, + * if the #ClutterLayoutManager used by @box has them + * + * This function is a wrapper around clutter_container_add_actor() + * and clutter_layout_manager_child_set() + * + * Language bindings should use the vector-based clutter_box_addv() + * variant instead + * + * Since: 1.2 + */ +void +clutter_box_pack (ClutterBox *box, + ClutterActor *actor, + const gchar *first_property, + ...) +{ + ClutterBoxPrivate *priv; + ClutterContainer *container; + ClutterLayoutMeta *meta; + GObjectClass *klass; + const gchar *pname; + va_list var_args; + + g_return_if_fail (CLUTTER_IS_BOX (box)); + g_return_if_fail (CLUTTER_IS_ACTOR (actor)); + + container = CLUTTER_CONTAINER (box); + clutter_container_add_actor (container, actor); + + if (first_property == NULL || *first_property == '\0') + return; + + priv = box->priv; + + meta = clutter_layout_manager_get_child_meta (priv->manager, + container, + actor); + + if (meta == NULL) + return; + + klass = G_OBJECT_GET_CLASS (meta); + + va_start (var_args, first_property); + + pname = first_property; + while (pname) + { + GValue value = { 0, }; + GParamSpec *pspec; + gchar *error; + + pspec = g_object_class_find_property (klass, pname); + if (pspec == NULL) + { + g_warning ("%s: the layout property '%s' for managers " + "of type '%s' (meta type '%s') does not exist", + G_STRLOC, + pname, + G_OBJECT_TYPE_NAME (priv->manager), + G_OBJECT_TYPE_NAME (meta)); + break; + } + + if (!(pspec->flags & G_PARAM_WRITABLE)) + { + g_warning ("%s: the layout property '%s' for managers " + "of type '%s' (meta type '%s') is not writable", + G_STRLOC, + pspec->name, + G_OBJECT_TYPE_NAME (priv->manager), + G_OBJECT_TYPE_NAME (meta)); + break; + } + + g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (pspec)); + G_VALUE_COLLECT (&value, var_args, 0, &error); + if (error) + { + g_warning ("%s: %s", G_STRLOC, error); + g_free (error); + break; + } + + clutter_layout_manager_child_set_property (priv->manager, + container, actor, + pspec->name, &value); + + g_value_unset (&value); + + pname = va_arg (var_args, gchar*); + } + + va_end (var_args); +} + +/** + * clutter_box_set_color: + * @box: a #ClutterBox + * @color: (allow-none): the background color, or %NULL to unset + * + * Sets (or unsets) the background color for @box + * + * Since: 1.2 + */ +void +clutter_box_set_color (ClutterBox *box, + const ClutterColor *color) +{ + ClutterBoxPrivate *priv; + + g_return_if_fail (CLUTTER_IS_BOX (box)); + + priv = box->priv; + + if (color) + { + priv->color = *color; + priv->color_set = TRUE; + } + else + priv->color_set = FALSE; + + clutter_actor_queue_redraw (CLUTTER_ACTOR (box)); + + g_object_notify (G_OBJECT (box), "color-set"); + g_object_notify (G_OBJECT (box), "color"); +} + +/** + * clutter_box_get_color: + * @box: a #ClutterBox + * @color: (out): return location for a #ClutterColor + * + * Retrieves the background color of @box + * + * If the #ClutterBox:color-set property is set to %FALSE the + * returned #ClutterColor is undefined + * + * Since: 1.2 + */ +void +clutter_box_get_color (ClutterBox *box, + ClutterColor *color) +{ + g_return_if_fail (CLUTTER_IS_BOX (box)); + g_return_if_fail (color != NULL); + + *color = box->priv->color; +} diff --git a/clutter/clutter-box.h b/clutter/clutter-box.h new file mode 100644 index 000000000..a44c274c1 --- /dev/null +++ b/clutter/clutter-box.h @@ -0,0 +1,62 @@ +#if !defined(__CLUTTER_H_INSIDE__) && !defined(CLUTTER_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __CLUTTER_BOX_H__ +#define __CLUTTER_BOX_H__ + +#include +#include +#include + +G_BEGIN_DECLS + +#define CLUTTER_TYPE_BOX (clutter_box_get_type ()) +#define CLUTTER_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CLUTTER_TYPE_BOX, ClutterBox)) +#define CLUTTER_IS_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CLUTTER_TYPE_BOX)) +#define CLUTTER_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CLUTTER_TYPE_BOX, ClutterBoxClass)) +#define CLUTTER_IS_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CLUTTER_TYPE_BOX)) +#define CLUTTER_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CLUTTER_TYPE_BOX, ClutterBoxClass)) + +typedef struct _ClutterBox ClutterBox; +typedef struct _ClutterBoxPrivate ClutterBoxPrivate; +typedef struct _ClutterBoxClass ClutterBoxClass; + +struct _ClutterBox +{ + ClutterActor parent_instance; + + ClutterBoxPrivate *priv; +}; + +struct _ClutterBoxClass +{ + ClutterActorClass parent_class; +}; + +GType clutter_box_get_type (void) G_GNUC_CONST; + +ClutterActor * clutter_box_new (ClutterLayoutManager *manager); + +void clutter_box_set_layout_manager (ClutterBox *box, + ClutterLayoutManager *manager); +ClutterLayoutManager *clutter_box_get_layout_manager (ClutterBox *box); + +void clutter_box_set_color (ClutterBox *box, + const ClutterColor *color); +void clutter_box_get_color (ClutterBox *box, + ClutterColor *color); + +void clutter_box_pack (ClutterBox *box, + ClutterActor *actor, + const gchar *first_property, + ...) G_GNUC_NULL_TERMINATED; +void clutter_box_packv (ClutterBox *box, + ClutterActor *actor, + guint n_properties, + const gchar * const properties[], + const GValue *values); + +G_END_DECLS + +#endif /* __CLUTTER_BOX_H__ */ diff --git a/clutter/clutter-fixed-layout.c b/clutter/clutter-fixed-layout.c new file mode 100644 index 000000000..5ec56fa3d --- /dev/null +++ b/clutter/clutter-fixed-layout.c @@ -0,0 +1,186 @@ +/* + * Clutter. + * + * An OpenGL based 'interactive canvas' library. + * + * Copyright (C) 2009 Intel Corporation. + * + * 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 . + * + * Author: + * Emmanuele Bassi + * + * Based on the fixed layout code inside clutter-group.c + */ + +/** + * SECTION:clutter-fixed-layout + * @short_description: A fixed layout manager + * + * #ClutterFixedLayout is a layout manager implementing the same + * layout policies as #ClutterGroup. + * + * #ClutterFixedLayout is available since Clutter 1.2 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "clutter-debug.h" +#include "clutter-fixed-layout.h" +#include "clutter-private.h" + +G_DEFINE_TYPE (ClutterFixedLayout, + clutter_fixed_layout, + CLUTTER_TYPE_LAYOUT_MANAGER); + +static void +clutter_fixed_layout_get_preferred_width (ClutterLayoutManager *manager, + ClutterContainer *container, + gfloat for_height, + gfloat *min_width_p, + gfloat *nat_width_p) +{ + GList *children, *l; + gdouble min_right; + gdouble natural_right; + + min_right = 0; + natural_right = 0; + + children = clutter_container_get_children (container); + + 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 (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; + } + + g_list_free (children); + + if (min_width_p) + *min_width_p = min_right; + + if (nat_width_p) + *nat_width_p = natural_right; +} + +static void +clutter_fixed_layout_get_preferred_height (ClutterLayoutManager *manager, + ClutterContainer *container, + gfloat for_width, + gfloat *min_height_p, + gfloat *nat_height_p) +{ + GList *children, *l; + gdouble min_bottom; + gdouble natural_bottom; + + min_bottom = 0; + natural_bottom = 0; + + children = clutter_container_get_children (container); + + 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 (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; + } + + g_list_free (children); + + if (min_height_p) + *min_height_p = min_bottom; + + if (nat_height_p) + *nat_height_p = natural_bottom; +} + +static void +clutter_fixed_layout_allocate (ClutterLayoutManager *manager, + ClutterContainer *container, + const ClutterActorBox *allocation, + ClutterAllocationFlags flags) +{ + GList *children, *l; + + children = clutter_container_get_children (container); + + for (l = children; l != NULL; l = l->next) + { + ClutterActor *child = l->data; + + clutter_actor_allocate_preferred_size (child, flags); + } + + g_list_free (children); +} + +static void +clutter_fixed_layout_class_init (ClutterFixedLayoutClass *klass) +{ + ClutterLayoutManagerClass *manager_class = + CLUTTER_LAYOUT_MANAGER_CLASS (klass); + + manager_class->get_preferred_width = + clutter_fixed_layout_get_preferred_width; + manager_class->get_preferred_height = + clutter_fixed_layout_get_preferred_height; + manager_class->allocate = clutter_fixed_layout_allocate; +} + +static void +clutter_fixed_layout_init (ClutterFixedLayout *self) +{ +} + +/** + * clutter_fixed_layout_new: + * + * Creates a new #ClutterFixedLayout + * + * Return value: the newly created #ClutterFixedLayout + * + * Since: 1.2 + */ +ClutterLayoutManager * +clutter_fixed_layout_new (void) +{ + return g_object_new (CLUTTER_TYPE_FIXED_LAYOUT, NULL); +} diff --git a/clutter/clutter-fixed-layout.h b/clutter/clutter-fixed-layout.h new file mode 100644 index 000000000..32273bc00 --- /dev/null +++ b/clutter/clutter-fixed-layout.h @@ -0,0 +1,80 @@ +/* + * Clutter. + * + * An OpenGL based 'interactive canvas' library. + * + * Copyright (C) 2009 Intel Corporation. + * + * 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 . + * + * Author: + * Emmanuele Bassi + */ + +#if !defined(__CLUTTER_H_INSIDE__) && !defined(CLUTTER_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __CLUTTER_FIXED_LAYOUT_H__ +#define __CLUTTER_FIXED_LAYOUT_H__ + +#include + +G_BEGIN_DECLS + +#define CLUTTER_TYPE_FIXED_LAYOUT (clutter_fixed_layout_get_type ()) +#define CLUTTER_FIXED_LAYOUT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CLUTTER_TYPE_FIXED_LAYOUT, ClutterFixedLayout)) +#define CLUTTER_IS_FIXED_LAYOUT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CLUTTER_TYPE_FIXED_LAYOUT)) +#define CLUTTER_FIXED_LAYOUT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CLUTTER_TYPE_FIXED_LAYOUT, ClutterFixedLayoutClass)) +#define CLUTTER_IS_FIXED_LAYOUT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CLUTTER_TYPE_FIXED_LAYOUT)) +#define CLUTTER_FIXED_LAYOUT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CLUTTER_TYPE_FIXED_LAYOUT, ClutterFixedLayoutClass)) + +typedef struct _ClutterFixedLayout ClutterFixedLayout; +typedef struct _ClutterFixedLayoutClass ClutterFixedLayoutClass; + +/** + * ClutterFixedLayout: + * + * The #ClutterFixedLayout structure contains only private data and + * it should be accessed using the provided API + * + * Since: 1.2 + */ +struct _ClutterFixedLayout +{ + /*< private >*/ + ClutterLayoutManager parent_instance; +}; + +/** + * ClutterFixedLayoutClass: + * + * The #ClutterFixedLayoutClass structure contains only private data + * and it should be accessed using the provided API + * + * Since: 1.2 + */ +struct _ClutterFixedLayoutClass +{ + /*< private >*/ + ClutterLayoutManagerClass parent_class; +}; + +GType clutter_fixed_layout_get_type (void) G_GNUC_CONST; + +ClutterLayoutManager *clutter_fixed_layout_new (void); + +G_END_DECLS + +#endif /* __CLUTTER_FIXED_LAYOUT_H__ */ diff --git a/clutter/clutter-flow-layout.c b/clutter/clutter-flow-layout.c new file mode 100644 index 000000000..bc81a679d --- /dev/null +++ b/clutter/clutter-flow-layout.c @@ -0,0 +1,1321 @@ +/* + * Clutter. + * + * An OpenGL based 'interactive canvas' library. + * + * Copyright (C) 2009 Intel Corporation. + * + * 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 . + * + * Author: + * Emmanuele Bassi + */ + +/** + * SECTION:clutter-flow-layout + * @short_description: A reflowing layout manager + * + * #ClutterFlowLayout is a layout manager which implements the following + * policy: + * + * + * the preferred natural size depends on the value + * of the #ClutterFlowLayout:orientation property; the layout will try + * to maintain all its children on a single row or + * column; + * if either the width or the height allocated are + * smaller than the preferred ones, the layout will wrap; in this case, + * the preferred height or width, respectively, will take into account + * the amount of columns and rows; + * each line (either column or row) in reflowing will + * have the size of the biggest cell on that line; if the + * #ClutterFlowLayout:homogeneous property is set to %FALSE the actor + * will be allocated within that area, and if set to %TRUE instead the + * actor will be given exactly that area; + * the size of the columns or rows can be controlled + * for both minimum and maximum; the spacing can also be controlled + * in both columns and rows. + * + * + * #ClutterFlowLayout is available since Clutter 1.2 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "clutter-actor.h" +#include "clutter-animatable.h" +#include "clutter-child-meta.h" +#include "clutter-debug.h" +#include "clutter-enum-types.h" +#include "clutter-flow-layout.h" +#include "clutter-layout-meta.h" +#include "clutter-private.h" + +#define CLUTTER_FLOW_LAYOUT_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLUTTER_TYPE_FLOW_LAYOUT, ClutterFlowLayoutPrivate)) + +struct _ClutterFlowLayoutPrivate +{ + ClutterContainer *container; + + ClutterFlowOrientation orientation; + + gfloat col_spacing; + gfloat row_spacing; + + gfloat min_col_width; + gfloat max_col_width; + gfloat col_width; + + gfloat min_row_height; + gfloat max_row_height; + gfloat row_height; + + /* per-line size */ + GArray *line_min; + GArray *line_natural; + + guint line_count; + + guint is_homogeneous : 1; +}; + +enum +{ + PROP_0, + + PROP_ORIENTATION, + + PROP_HOMOGENEOUS, + + PROP_COLUMN_SPACING, + PROP_ROW_SPACING, + + PROP_MIN_COLUMN_WIDTH, + PROP_MAX_COLUMN_WIDTH, + PROP_MIN_ROW_HEGHT, + PROP_MAX_ROW_HEIGHT +}; + +G_DEFINE_TYPE (ClutterFlowLayout, + clutter_flow_layout, + CLUTTER_TYPE_LAYOUT_MANAGER); + +static gint +get_columns (ClutterFlowLayout *self, + gfloat for_width) +{ + ClutterFlowLayoutPrivate *priv = self->priv; + gint n_columns; + + if (for_width < 0) + return 1; + + if (priv->col_width == 0) + return 1; + + n_columns = (gint) (for_width + priv->col_spacing) + / (priv->col_width + priv->col_spacing); + + if (n_columns == 0) + return 1; + + return n_columns; +} + +static gint +get_rows (ClutterFlowLayout *self, + gfloat for_height) +{ + ClutterFlowLayoutPrivate *priv = self->priv; + gint n_rows; + + if (for_height < 0) + return 1; + + if (priv->row_height == 0) + return 1; + + n_rows = (gint) (for_height + priv->row_spacing) + / (priv->row_height + priv->row_spacing); + + if (n_rows == 0) + return 1; + + return n_rows; +} + +static gint +compute_lines (ClutterFlowLayout *self, + gfloat avail_width, + gfloat avail_height) +{ + ClutterFlowLayoutPrivate *priv = self->priv; + + if (priv->orientation == CLUTTER_FLOW_HORIZONTAL) + return get_columns (self, avail_width); + else + return get_rows (self, avail_height); +} + +static void +clutter_flow_layout_get_preferred_width (ClutterLayoutManager *manager, + ClutterContainer *container, + gfloat for_height, + gfloat *min_width_p, + gfloat *nat_width_p) +{ + ClutterFlowLayoutPrivate *priv = CLUTTER_FLOW_LAYOUT (manager)->priv; + GList *l, *children = clutter_container_get_children (container); + gint n_rows, line_item_count, line_count; + gfloat total_min_width, total_natural_width; + gfloat line_min_width, line_natural_width; + gfloat max_min_width, max_natural_width; + gfloat item_y; + + n_rows = get_rows (CLUTTER_FLOW_LAYOUT (manager), for_height); + + total_min_width = 0; + total_natural_width = 0; + + line_min_width = 0; + line_natural_width = 0; + + line_item_count = 0; + line_count = 0; + + item_y = 0; + + /* clear the line width arrays */ + if (priv->orientation == CLUTTER_FLOW_VERTICAL && for_height > 0) + { + if (priv->line_min != NULL) + g_array_free (priv->line_min, TRUE); + + if (priv->line_natural != NULL) + g_array_free (priv->line_natural, TRUE); + + priv->line_min = g_array_sized_new (FALSE, FALSE, + sizeof (gfloat), + 16); + priv->line_natural = g_array_sized_new (FALSE, FALSE, + sizeof (gfloat), + 16); + } + + for (l = children; l != NULL; l = l->next) + { + ClutterActor *child = l->data; + gfloat child_min, child_natural; + gfloat new_y, item_height; + + if (!CLUTTER_ACTOR_IS_VISIBLE (child)) + continue; + + if (priv->orientation == CLUTTER_FLOW_VERTICAL && for_height > 0) + { + if (line_item_count == n_rows) + { + total_min_width += line_min_width; + total_natural_width += line_natural_width; + + g_array_append_val (priv->line_min, + line_min_width); + g_array_append_val (priv->line_natural, + line_natural_width); + + line_min_width = line_natural_width = 0; + + line_item_count = 0; + line_count += 1; + item_y = 0; + } + + new_y = ((line_item_count + 1) * (for_height + priv->row_spacing)) + / n_rows; + item_height = new_y - item_y - priv->row_spacing; + + clutter_actor_get_preferred_width (child, item_height, + &child_min, + &child_natural); + + line_min_width = MAX (line_min_width, child_min); + line_natural_width = MAX (line_natural_width, child_natural); + + item_y = new_y; + line_item_count += 1; + + max_min_width = MAX (max_min_width, line_min_width); + max_natural_width = MAX (max_natural_width, line_natural_width); + } + else + { + clutter_actor_get_preferred_width (child, for_height, + &child_min, + &child_natural); + + max_min_width = MAX (max_min_width, child_min); + max_natural_width = MAX (max_natural_width, child_natural); + + line_count += 1; + } + } + + g_list_free (children); + + priv->col_width = max_natural_width; + + if (priv->max_col_width > 0 && priv->col_width > priv->max_col_width) + priv->col_width = MAX (priv->max_col_width, max_min_width); + + if (priv->col_width < priv->min_col_width) + priv->col_width = priv->min_col_width; + + if (priv->orientation == CLUTTER_FLOW_VERTICAL && for_height > 0) + { + /* if we have a non-full row we need to add it */ + if (line_item_count > 0) + { + total_min_width += line_min_width; + total_natural_width += line_natural_width; + + g_array_append_val (priv->line_min, + line_min_width); + g_array_append_val (priv->line_natural, + line_natural_width); + } + + priv->line_count = line_count; + if (priv->line_count > 0) + { + gfloat total_spacing; + + total_spacing = priv->col_spacing * (priv->line_count - 1); + + total_min_width += total_spacing; + total_natural_width += total_spacing; + } + } + + if (min_width_p) + *min_width_p = total_min_width; + + if (nat_width_p) + *nat_width_p = total_natural_width; +} + +static void +clutter_flow_layout_get_preferred_height (ClutterLayoutManager *manager, + ClutterContainer *container, + gfloat for_width, + gfloat *min_height_p, + gfloat *nat_height_p) +{ + ClutterFlowLayoutPrivate *priv = CLUTTER_FLOW_LAYOUT (manager)->priv; + GList *l, *children = clutter_container_get_children (container); + gint n_columns, line_item_count, line_count; + gfloat total_min_height, total_natural_height; + gfloat line_min_height, line_natural_height; + gfloat max_min_height, max_natural_height; + gfloat item_x; + + n_columns = get_columns (CLUTTER_FLOW_LAYOUT (manager), for_width); + + total_min_height = 0; + total_natural_height = 0; + + line_min_height = 0; + line_natural_height = 0; + + line_item_count = 0; + line_count = 0; + + item_x = 0; + + /* clear the line height arrays */ + if (priv->orientation == CLUTTER_FLOW_HORIZONTAL && for_width > 0) + { + if (priv->line_min != NULL) + g_array_free (priv->line_min, TRUE); + + if (priv->line_natural != NULL) + g_array_free (priv->line_natural, TRUE); + + priv->line_min = g_array_sized_new (FALSE, FALSE, + sizeof (gfloat), + 16); + priv->line_natural = g_array_sized_new (FALSE, FALSE, + sizeof (gfloat), + 16); + } + + for (l = children; l != NULL; l = l->next) + { + ClutterActor *child = l->data; + gfloat child_min, child_natural; + gfloat new_x, item_width; + + if (!CLUTTER_ACTOR_IS_VISIBLE (child)) + continue; + + if (priv->orientation == CLUTTER_FLOW_HORIZONTAL && for_width > 0) + { + if (line_item_count == n_columns) + { + total_min_height += line_min_height; + total_natural_height += line_natural_height; + + g_array_append_val (priv->line_min, + line_min_height); + g_array_append_val (priv->line_natural, + line_natural_height); + + line_min_height = line_natural_height = 0; + + line_item_count = 0; + line_count += 1; + item_x = 0; + } + + new_x = ((line_item_count + 1) * (for_width + priv->col_spacing)) + / n_columns; + item_width = new_x - item_x - priv->col_spacing; + + clutter_actor_get_preferred_height (child, item_width, + &child_min, + &child_natural); + + line_min_height = MAX (line_min_height, child_min); + line_natural_height = MAX (line_natural_height, child_natural); + + item_x = new_x; + line_item_count += 1; + + max_min_height = MAX (max_min_height, line_min_height); + max_natural_height = MAX (max_natural_height, line_natural_height); + } + else + { + clutter_actor_get_preferred_height (child, for_width, + &child_min, + &child_natural); + + max_min_height = MAX (max_min_height, child_min); + max_natural_height = MAX (max_natural_height, child_natural); + + line_count += 1; + } + } + + g_list_free (children); + + priv->row_height = max_natural_height; + + if (priv->max_row_height > 0 && priv->row_height > priv->max_row_height) + priv->row_height = MAX (priv->max_row_height, max_min_height); + + if (priv->row_height < priv->min_row_height) + priv->row_height = priv->min_row_height; + + if (priv->orientation == CLUTTER_FLOW_HORIZONTAL && for_width > 0) + { + /* if we have a non-full row we need to add it */ + if (line_item_count > 0) + { + total_min_height += line_min_height; + total_natural_height += line_natural_height; + + g_array_append_val (priv->line_min, + line_min_height); + g_array_append_val (priv->line_natural, + line_natural_height); + } + + priv->line_count = line_count; + if (priv->line_count > 0) + { + gfloat total_spacing; + + total_spacing = priv->row_spacing * (priv->line_count - 1); + + total_min_height += total_spacing; + total_natural_height += total_spacing; + } + } + + if (min_height_p) + *min_height_p = total_min_height; + + if (nat_height_p) + *nat_height_p = total_natural_height; +} + +static void +clutter_flow_layout_allocate (ClutterLayoutManager *manager, + ClutterContainer *container, + const ClutterActorBox *allocation, + ClutterAllocationFlags flags) +{ + ClutterFlowLayoutPrivate *priv = CLUTTER_FLOW_LAYOUT (manager)->priv; + GList *l, *children = clutter_container_get_children (container); + gfloat avail_width, avail_height; + gfloat item_x, item_y; + gint line_item_count; + gint items_per_line; + gint line_index; + + if (children == NULL) + return; + + clutter_actor_box_get_size (allocation, &avail_width, &avail_height); + + items_per_line = compute_lines (CLUTTER_FLOW_LAYOUT (manager), + avail_width, avail_height); + + item_x = item_y = 0; + + line_item_count = 0; + line_index = 0; + + for (l = children; l != NULL; l = l->next) + { + ClutterActor *child = l->data; + ClutterActorBox child_alloc; + gfloat item_width, item_height; + gfloat new_x, new_y; + + if (!CLUTTER_ACTOR_IS_VISIBLE (child)) + continue; + + if (priv->orientation == CLUTTER_FLOW_HORIZONTAL) + { + if (line_item_count == items_per_line && line_item_count > 0) + { + item_y += g_array_index (priv->line_natural, + gfloat, + line_index); + + line_item_count = 0; + line_index += 1; + + item_x = 0; + } + + new_x = ((line_item_count + 1) * (avail_width + priv->col_spacing)) + / items_per_line; + item_width = new_x - item_x - priv->col_spacing; + item_height = g_array_index (priv->line_natural, + gfloat, + line_index); + + if (!priv->is_homogeneous) + { + gfloat child_min, child_natural; + + clutter_actor_get_preferred_width (child, item_height, + &child_min, + &child_natural); + item_width = MIN (item_width, child_min); + + clutter_actor_get_preferred_height (child, item_width, + &child_min, + &child_natural); + item_height = MIN (item_height, child_natural); + } + } + else + { + if (line_item_count == items_per_line && line_item_count > 0) + { + item_x += g_array_index (priv->line_natural, + gfloat, + line_index); + + line_item_count = 0; + line_index += 1; + + item_y = 0; + } + + new_y = ((line_item_count + 1) * (avail_height + priv->row_spacing)) + / items_per_line; + item_height = new_y - item_y - priv->row_spacing; + item_width = g_array_index (priv->line_natural, + gfloat, + line_index); + + if (!priv->is_homogeneous) + { + gfloat child_min, child_natural; + + clutter_actor_get_preferred_width (child, item_height, + &child_min, + &child_natural); + item_width = MIN (item_width, child_min); + + clutter_actor_get_preferred_height (child, item_width, + &child_min, + &child_natural); + item_height = MIN (item_height, child_natural); + } + } + + CLUTTER_NOTE (LAYOUT, + "flow[line:%d, item:%d/%d] =" + "{ %.2f, %.2f, %.2f, %.2f }", + line_index, line_item_count + 1, items_per_line, + item_x, item_y, item_width, item_height); + + child_alloc.x1 = ceil (item_x); + child_alloc.y1 = ceil (item_y); + child_alloc.x2 = ceil (child_alloc.x1 + item_width); + child_alloc.y2 = ceil (child_alloc.y1 + item_height); + clutter_actor_allocate (child, &child_alloc, flags); + + if (priv->orientation == CLUTTER_FLOW_HORIZONTAL) + item_x = new_x; + else + item_y = new_y; + + line_item_count += 1; + } + + g_list_free (children); +} + +static void +clutter_flow_layout_set_container (ClutterLayoutManager *manager, + ClutterContainer *container) +{ + ClutterFlowLayoutPrivate *priv = CLUTTER_FLOW_LAYOUT (manager)->priv; + + priv->container = container; + + if (priv->container != NULL) + { + ClutterRequestMode request_mode; + + /* we need to change the :request-mode of the container + * to match the orientation + */ + request_mode = (priv->orientation == CLUTTER_FLOW_HORIZONTAL) + ? CLUTTER_REQUEST_HEIGHT_FOR_WIDTH + : CLUTTER_REQUEST_WIDTH_FOR_HEIGHT; + clutter_actor_set_request_mode (CLUTTER_ACTOR (priv->container), + request_mode); + } +} + +static void +clutter_flow_layout_set_property (GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + ClutterFlowLayout *self = CLUTTER_FLOW_LAYOUT (gobject); + + switch (prop_id) + { + case PROP_ORIENTATION: + clutter_flow_layout_set_orientation (self, g_value_get_enum (value)); + break; + + case PROP_HOMOGENEOUS: + clutter_flow_layout_set_homogeneous (self, g_value_get_boolean (value)); + break; + + case PROP_COLUMN_SPACING: + clutter_flow_layout_set_column_spacing (self, g_value_get_float (value)); + break; + + case PROP_ROW_SPACING: + clutter_flow_layout_set_row_spacing (self, g_value_get_float (value)); + break; + + case PROP_MIN_COLUMN_WIDTH: + clutter_flow_layout_set_column_width (self, + g_value_get_float (value), + self->priv->max_col_width); + break; + + case PROP_MAX_COLUMN_WIDTH: + clutter_flow_layout_set_column_width (self, + self->priv->min_col_width, + g_value_get_float (value)); + break; + + case PROP_MIN_ROW_HEGHT: + clutter_flow_layout_set_row_height (self, + g_value_get_float (value), + self->priv->max_row_height); + break; + + case PROP_MAX_ROW_HEIGHT: + clutter_flow_layout_set_row_height (self, + self->priv->min_row_height, + g_value_get_float (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +clutter_flow_layout_get_property (GObject *gobject, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + ClutterFlowLayoutPrivate *priv = CLUTTER_FLOW_LAYOUT (gobject)->priv; + + switch (prop_id) + { + case PROP_ORIENTATION: + g_value_set_enum (value, priv->orientation); + break; + + case PROP_HOMOGENEOUS: + g_value_set_boolean (value, priv->is_homogeneous); + break; + + case PROP_COLUMN_SPACING: + g_value_set_float (value, priv->col_spacing); + break; + + case PROP_ROW_SPACING: + g_value_set_float (value, priv->row_spacing); + break; + + case PROP_MIN_COLUMN_WIDTH: + g_value_set_float (value, priv->min_col_width); + break; + + case PROP_MAX_COLUMN_WIDTH: + g_value_set_float (value, priv->max_col_width); + break; + + case PROP_MIN_ROW_HEGHT: + g_value_set_float (value, priv->min_row_height); + break; + + case PROP_MAX_ROW_HEIGHT: + g_value_set_float (value, priv->max_row_height); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +clutter_flow_layout_finalize (GObject *gobject) +{ + ClutterFlowLayoutPrivate *priv = CLUTTER_FLOW_LAYOUT (gobject)->priv; + + if (priv->line_min != NULL) + g_array_free (priv->line_min, TRUE); + + if (priv->line_natural != NULL) + g_array_free (priv->line_natural, TRUE); + + G_OBJECT_CLASS (clutter_flow_layout_parent_class)->finalize (gobject); +} + +static void +clutter_flow_layout_class_init (ClutterFlowLayoutClass *klass) +{ + GObjectClass *gobject_class; + ClutterLayoutManagerClass *layout_class; + GParamSpec *pspec; + + g_type_class_add_private (klass, sizeof (ClutterFlowLayoutPrivate)); + + gobject_class = G_OBJECT_CLASS (klass); + layout_class = CLUTTER_LAYOUT_MANAGER_CLASS (klass); + + gobject_class->set_property = clutter_flow_layout_set_property; + gobject_class->get_property = clutter_flow_layout_get_property; + gobject_class->finalize = clutter_flow_layout_finalize; + + layout_class->get_preferred_width = + clutter_flow_layout_get_preferred_width; + layout_class->get_preferred_height = + clutter_flow_layout_get_preferred_height; + layout_class->allocate = clutter_flow_layout_allocate; + layout_class->set_container = clutter_flow_layout_set_container; + + /** + * ClutterFlowLayout:orientation: + * + * The orientation of the #ClutterFlowLayout. The children + * of the layout will be layed out following the orientation. + * + * This property also controls the overflowing directions + * + * Since: 1.2 + */ + pspec = g_param_spec_enum ("orientation", + "Orientation", + "The orientation of the layout", + CLUTTER_TYPE_FLOW_ORIENTATION, + CLUTTER_FLOW_HORIZONTAL, + CLUTTER_PARAM_READWRITE | + G_PARAM_CONSTRUCT); + g_object_class_install_property (gobject_class, PROP_ORIENTATION, pspec); + + /** + * ClutterFlowLayout:homogeneous: + * + * Whether each child inside the #ClutterFlowLayout should receive + * the same allocation + * + * Since: 1.2 + */ + pspec = g_param_spec_boolean ("homogeneous", + "Homogeneous", + "Whether each item should receive the " + "same allocation", + FALSE, + CLUTTER_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_HOMOGENEOUS, pspec); + + /** + * ClutterFlowLayout:column-spacing: + * + * The spacing between columns, in pixels; the value of this + * property is honoured by horizontal non-overflowing layouts + * and by vertical overflowing layouts + * + * Since: 1.2 + */ + pspec = g_param_spec_float ("column-spacing", + "Column Spacing", + "The spacing between columns", + 0.0, G_MAXFLOAT, + 0.0, + CLUTTER_PARAM_READWRITE); + g_object_class_install_property (gobject_class, + PROP_COLUMN_SPACING, + pspec); + + /** + * ClutterFlowLayout:row-spacing: + * + * The spacing between rows, in pixels; the value of this + * property is honoured by vertical non-overflowing layouts and + * by horizontal overflowing layouts + * + * Since: 1.2 + */ + pspec = g_param_spec_float ("row-spacing", + "Row Spacing", + "The spacing between rows", + 0.0, G_MAXFLOAT, + 0.0, + CLUTTER_PARAM_READWRITE); + g_object_class_install_property (gobject_class, + PROP_ROW_SPACING, + pspec); + + /** + * ClutterFlowLayout:min-column-width: + * + * Minimum width for each column in the layout, in pixels + * + * Since: 1.2 + */ + pspec = g_param_spec_float ("min-column-width", + "Minimum Column Width", + "Minimum width for each column", + 0.0, G_MAXFLOAT, + 0.0, + CLUTTER_PARAM_READWRITE); + g_object_class_install_property (gobject_class, + PROP_MIN_COLUMN_WIDTH, + pspec); + + /** + * ClutterFlowLayout:max-column-width: + * + * Maximum width for each column in the layout, in pixels. If + * set to -1 the width will be the maximum child width + * + * Since: 1.2 + */ + pspec = g_param_spec_float ("max-column-width", + "Maximum Column Width", + "Maximum width for each column", + -1.0, G_MAXFLOAT, + -1.0, + CLUTTER_PARAM_READWRITE); + g_object_class_install_property (gobject_class, + PROP_MAX_COLUMN_WIDTH, + pspec); + + /** + * ClutterFlowLayout:min-row-height: + * + * Minimum height for each row in the layout, in pixels + * + * Since: 1.2 + */ + pspec = g_param_spec_float ("min-row-height", + "Minimum Row Height", + "Minimum height for each row", + 0.0, G_MAXFLOAT, + 0.0, + CLUTTER_PARAM_READWRITE); + g_object_class_install_property (gobject_class, + PROP_MIN_ROW_HEGHT, + pspec); + + /** + * ClutterFlowLayout:max-row-height: + * + * Maximum height for each row in the layout, in pixels. If + * set to -1 the width will be the maximum child height + * + * Since: 1.2 + */ + pspec = g_param_spec_float ("max-row-height", + "Maximum Row Height", + "Maximum height for each row", + -1.0, G_MAXFLOAT, + -1.0, + CLUTTER_PARAM_READWRITE); + g_object_class_install_property (gobject_class, + PROP_MAX_ROW_HEIGHT, + pspec); +} + +static void +clutter_flow_layout_init (ClutterFlowLayout *self) +{ + ClutterFlowLayoutPrivate *priv; + + self->priv = priv = CLUTTER_FLOW_LAYOUT_GET_PRIVATE (self); + + priv->orientation = CLUTTER_FLOW_HORIZONTAL; + + priv->col_spacing = 0; + priv->row_spacing = 0; + + priv->min_col_width = priv->min_row_height = 0; + priv->max_col_width = priv->max_row_height = -1; + + priv->line_min = NULL; + priv->line_natural = NULL; +} + +/** + * clutter_flow_layout_new: + * @orientation: the orientation of the flow layout + * + * Creates a new #ClutterFlowLayout with the given @orientation + * + * Return value: the newly created #ClutterFlowLayout + * + * Since: 1.2 + */ +ClutterLayoutManager * +clutter_flow_layout_new (ClutterFlowOrientation orientation) +{ + return g_object_new (CLUTTER_TYPE_FLOW_LAYOUT, + "orientation", orientation, + NULL); +} + +/** + * clutter_flow_layout_set_orientation: + * @layout: a #ClutterFlowLayout + * @orientation: the orientation of the layout + * + * Sets the orientation of the flow layout + * + * The orientation controls the direction used to allocate + * the children: either horizontally or vertically. The + * orientation also controls the direction of the overflowing + * + * Since: 1.2 + */ +void +clutter_flow_layout_set_orientation (ClutterFlowLayout *layout, + ClutterFlowOrientation orientation) +{ + ClutterFlowLayoutPrivate *priv; + + g_return_if_fail (CLUTTER_IS_FLOW_LAYOUT (layout)); + + priv = layout->priv; + + if (priv->orientation != orientation) + { + ClutterLayoutManager *manager; + + priv->orientation = orientation; + + if (priv->container != NULL) + { + ClutterRequestMode request_mode; + + /* we need to change the :request-mode of the container + * to match the orientation + */ + request_mode = (priv->orientation == CLUTTER_FLOW_HORIZONTAL) + ? CLUTTER_REQUEST_HEIGHT_FOR_WIDTH + : CLUTTER_REQUEST_WIDTH_FOR_HEIGHT; + clutter_actor_set_request_mode (CLUTTER_ACTOR (priv->container), + request_mode); + } + + manager = CLUTTER_LAYOUT_MANAGER (layout); + clutter_layout_manager_layout_changed (manager); + + g_object_notify (G_OBJECT (layout), "orientation"); + } +} + +/** + * clutter_flow_layout_get_orientation: + * @layout: a #ClutterFlowLayout + * + * Retrieves the orientation of the @layout + * + * Return value: the orientation of the #ClutterFlowLayout + * + * Since: 1.2 + */ +ClutterFlowOrientation +clutter_flow_layout_get_orientation (ClutterFlowLayout *layout) +{ + g_return_val_if_fail (CLUTTER_IS_FLOW_LAYOUT (layout), + CLUTTER_FLOW_HORIZONTAL); + + return layout->priv->orientation; +} + +/** + * clutter_flow_layout_set_homogeneous: + * @layout: a #ClutterFlowLayout + * @homogeneous: whether the layout should be homogeneous or not + * + * Sets whether the @layout should allocate the same space for + * each child + * + * Since: 1.2 + */ +void +clutter_flow_layout_set_homogeneous (ClutterFlowLayout *layout, + gboolean homogeneous) +{ + ClutterFlowLayoutPrivate *priv; + + g_return_if_fail (CLUTTER_IS_FLOW_LAYOUT (layout)); + + priv = layout->priv; + + if (priv->is_homogeneous != homogeneous) + { + ClutterLayoutManager *manager; + + priv->is_homogeneous = homogeneous; + + manager = CLUTTER_LAYOUT_MANAGER (layout); + clutter_layout_manager_layout_changed (manager); + + g_object_notify (G_OBJECT (layout), "homogeneous"); + } +} + +/** + * clutter_flow_layout_get_homogeneous: + * @layout: a #ClutterFlowLayout + * + * Retrieves whether the @layout is homogeneous + * + * Return value: %TRUE if the #ClutterFlowLayout is homogeneous + * + * Since: 1.2 + */ +gboolean +clutter_flow_layout_get_homogeneous (ClutterFlowLayout *layout) +{ + g_return_val_if_fail (CLUTTER_IS_FLOW_LAYOUT (layout), FALSE); + + return layout->priv->is_homogeneous; +} + +/** + * clutter_flow_layout_set_column_spacing: + * @layout: a #ClutterFlowLayout + * @spacing: the space between columns + * + * Sets the space between columns, in pixels + * + * Since: 1.2 + */ +void +clutter_flow_layout_set_column_spacing (ClutterFlowLayout *layout, + gfloat spacing) +{ + ClutterFlowLayoutPrivate *priv; + + g_return_if_fail (CLUTTER_IS_FLOW_LAYOUT (layout)); + + priv = layout->priv; + + if (priv->col_spacing != spacing) + { + ClutterLayoutManager *manager; + + priv->col_spacing = spacing; + + manager = CLUTTER_LAYOUT_MANAGER (layout); + clutter_layout_manager_layout_changed (manager); + + g_object_notify (G_OBJECT (layout), "column-spacing"); + } +} + +/** + * clutter_flow_layout_get_column_spacing: + * @layout: a #ClutterFlowLayout + * + * Retrieves the spacing between columns + * + * Return value: the spacing between columns of the #ClutterFlowLayout, + * in pixels + * + * Since: 1.2 + */ +gfloat +clutter_flow_layout_get_column_spacing (ClutterFlowLayout *layout) +{ + g_return_val_if_fail (CLUTTER_IS_FLOW_LAYOUT (layout), 0.0); + + return layout->priv->col_spacing; +} + +/** + * clutter_flow_layout_set_row_spacing: + * @layout: a #ClutterFlowLayout + * @spacing: the space between rows + * + * Sets the spacing between rows, in pixels + * + * Since: 1.2 + */ +void +clutter_flow_layout_set_row_spacing (ClutterFlowLayout *layout, + gfloat spacing) +{ + ClutterFlowLayoutPrivate *priv; + + g_return_if_fail (CLUTTER_IS_FLOW_LAYOUT (layout)); + + priv = layout->priv; + + if (priv->row_spacing != spacing) + { + ClutterLayoutManager *manager; + + priv->row_spacing = spacing; + + manager = CLUTTER_LAYOUT_MANAGER (layout); + clutter_layout_manager_layout_changed (manager); + + g_object_notify (G_OBJECT (layout), "row-spacing"); + } +} + +/** + * clutter_flow_layout_get_row_spacing: + * @layout: a #ClutterFlowLayout + * + * Retrieves the spacing between rows + * + * Return value: the spacing between rows of the #ClutterFlowLayout, + * in pixels + * + * Since: 1.2 + */ +gfloat +clutter_flow_layout_get_row_spacing (ClutterFlowLayout *layout) +{ + g_return_val_if_fail (CLUTTER_IS_FLOW_LAYOUT (layout), 0.0); + + return layout->priv->row_spacing; +} + +/** + * clutter_flow_layout_set_column_width: + * @layout: a #ClutterFlowLayout + * @min_width: minimum width of a column + * @max_width: maximum width of a column + * + * Sets the minimum and maximum widths that a column can have + * + * Since: 1.2 + */ +void +clutter_flow_layout_set_column_width (ClutterFlowLayout *layout, + gfloat min_width, + gfloat max_width) +{ + ClutterFlowLayoutPrivate *priv; + gboolean notify_min = FALSE, notify_max = FALSE; + + g_return_if_fail (CLUTTER_IS_FLOW_LAYOUT (layout)); + + priv = layout->priv; + + if (priv->min_col_width != min_width) + { + priv->min_col_width = min_width; + + notify_min = TRUE; + } + + if (priv->max_col_width != max_width) + { + priv->max_col_width = max_width; + + notify_max = TRUE; + } + + g_object_freeze_notify (G_OBJECT (layout)); + + if (notify_min || notify_max) + { + ClutterLayoutManager *manager = CLUTTER_LAYOUT_MANAGER (layout); + + clutter_layout_manager_layout_changed (manager); + } + + if (notify_min) + g_object_notify (G_OBJECT (layout), "min-column-width"); + + if (notify_max) + g_object_notify (G_OBJECT (layout), "max-column-width"); + + g_object_thaw_notify (G_OBJECT (layout)); +} + +/** + * clutter_flow_layout_get_column_width: + * @layout: a #ClutterFlowLayout + * @min_width: (out): return location for the minimum column width, or %NULL + * @max_width: (out): return location for the maximum column width, or %NULL + * + * Retrieves the minimum and maximum column widths + * + * Since: 1.2 + */ +void +clutter_flow_layout_get_column_width (ClutterFlowLayout *layout, + gfloat *min_width, + gfloat *max_width) +{ + g_return_if_fail (CLUTTER_IS_FLOW_LAYOUT (layout)); + + if (min_width) + *min_width = layout->priv->min_col_width; + + if (max_width) + *max_width = layout->priv->max_col_width; +} + +/** + * clutter_flow_layout_set_row_height: + * @layout: a #ClutterFlowLayout + * @min_height: the minimum height of a row + * @max_height: the maximum height of a row + * + * Sets the minimum and maximum heights that a row can have + * + * Since: 1.2 + */ +void +clutter_flow_layout_set_row_height (ClutterFlowLayout *layout, + gfloat min_height, + gfloat max_height) +{ + ClutterFlowLayoutPrivate *priv; + gboolean notify_min = FALSE, notify_max = FALSE; + + g_return_if_fail (CLUTTER_IS_FLOW_LAYOUT (layout)); + + priv = layout->priv; + + if (priv->min_row_height != min_height) + { + priv->min_row_height = min_height; + + notify_min = TRUE; + } + + if (priv->max_row_height != max_height) + { + priv->max_row_height = max_height; + + notify_max = TRUE; + } + + g_object_freeze_notify (G_OBJECT (layout)); + + if (notify_min || notify_max) + { + ClutterLayoutManager *manager = CLUTTER_LAYOUT_MANAGER (layout); + + clutter_layout_manager_layout_changed (manager); + } + + if (notify_min) + g_object_notify (G_OBJECT (layout), "min-row-height"); + + if (notify_max) + g_object_notify (G_OBJECT (layout), "max-row-height"); + + g_object_thaw_notify (G_OBJECT (layout)); +} + +/** + * clutter_flow_layout_get_row_height: + * @layout: a #ClutterFlowLayout + * @min_height: (out): return location for the minimum row height, or %NULL + * @max_height: (out): return location for the maximum row height, or %NULL + * + * Retrieves the minimum and maximum row heights + * + * Since: 1.2 + */ +void +clutter_flow_layout_get_row_height (ClutterFlowLayout *layout, + gfloat *min_height, + gfloat *max_height) +{ + g_return_if_fail (CLUTTER_IS_FLOW_LAYOUT (layout)); + + if (min_height) + *min_height = layout->priv->min_row_height; + + if (max_height) + *max_height = layout->priv->max_row_height; +} diff --git a/clutter/clutter-flow-layout.h b/clutter/clutter-flow-layout.h new file mode 100644 index 000000000..5cbbcd359 --- /dev/null +++ b/clutter/clutter-flow-layout.h @@ -0,0 +1,127 @@ +/* + * Clutter. + * + * An OpenGL based 'interactive canvas' library. + * + * Copyright (C) 2009 Intel Corporation. + * + * 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 . + * + * Author: + * Emmanuele Bassi + */ + +#if !defined(__CLUTTER_H_INSIDE__) && !defined(CLUTTER_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __CLUTTER_FLOW_LAYOUT_H__ +#define __CLUTTER_FLOW_LAYOUT_H__ + +#include + +G_BEGIN_DECLS + +#define CLUTTER_TYPE_FLOW_LAYOUT (clutter_flow_layout_get_type ()) +#define CLUTTER_FLOW_LAYOUT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CLUTTER_TYPE_FLOW_LAYOUT, ClutterFlowLayout)) +#define CLUTTER_IS_FLOW_LAYOUT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CLUTTER_TYPE_FLOW_LAYOUT)) +#define CLUTTER_FLOW_LAYOUT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CLUTTER_TYPE_FLOW_LAYOUT, ClutterFlowLayoutClass)) +#define CLUTTER_IS_FLOW_LAYOUT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CLUTTER_TYPE_FLOW_LAYOUT)) +#define CLUTTER_FLOW_LAYOUT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CLUTTER_TYPE_FLOW_LAYOUT, ClutterFlowLayoutClass)) + +typedef struct _ClutterFlowLayout ClutterFlowLayout; +typedef struct _ClutterFlowLayoutPrivate ClutterFlowLayoutPrivate; +typedef struct _ClutterFlowLayoutClass ClutterFlowLayoutClass; + +/** + * ClutterFlowOrientation: + * @CLUTTER_FLOW_HORIZONTAL: Arrange the children of the flow layout + * horizontally first + * @CLUTTER_FLOW_VERTICAL: Arrange the children of the flow layout + * vertically first + * + * The direction of the arrangement of the children inside + * a #ClutterFlowLayout + * + * Since: 1.2 + */ +typedef enum { /*< prefix=CLUTTER_FLOW >*/ + CLUTTER_FLOW_HORIZONTAL, + CLUTTER_FLOW_VERTICAL +} ClutterFlowOrientation; + +/** + * ClutterFlowLayout: + * + * The #ClutterFlowLayout structure contains only private data + * and should be accessed using the provided API + * + * Since: 1.2 + */ +struct _ClutterFlowLayout +{ + /*< private >*/ + ClutterLayoutManager parent_instance; + + ClutterFlowLayoutPrivate *priv; +}; + +/** + * ClutterFlowLayoutClass: + * + * The #ClutterFlowLayoutClass structure contains only private data + * and should be accessed using the provided API + * + * Since: 1.2 + */ +struct _ClutterFlowLayoutClass +{ + /*< private >*/ + ClutterLayoutManagerClass parent_class; +}; + +GType clutter_flow_layout_get_type (void) G_GNUC_CONST; + +ClutterLayoutManager * clutter_flow_layout_new (ClutterFlowOrientation orientation); + +void clutter_flow_layout_set_orientation (ClutterFlowLayout *layout, + ClutterFlowOrientation orientation); +ClutterFlowOrientation clutter_flow_layout_get_orientation (ClutterFlowLayout *layout); +void clutter_flow_layout_set_homogeneous (ClutterFlowLayout *layout, + gboolean homogeneous); +gboolean clutter_flow_layout_get_homogeneous (ClutterFlowLayout *layout); + +void clutter_flow_layout_set_column_spacing (ClutterFlowLayout *layout, + gfloat spacing); +gfloat clutter_flow_layout_get_column_spacing (ClutterFlowLayout *layout); +void clutter_flow_layout_set_row_spacing (ClutterFlowLayout *layout, + gfloat spacing); +gfloat clutter_flow_layout_get_row_spacing (ClutterFlowLayout *layout); + +void clutter_flow_layout_set_column_width (ClutterFlowLayout *layout, + gfloat min_width, + gfloat max_width); +void clutter_flow_layout_get_column_width (ClutterFlowLayout *layout, + gfloat *min_width, + gfloat *max_width); +void clutter_flow_layout_set_row_height (ClutterFlowLayout *layout, + gfloat min_height, + gfloat max_height); +void clutter_flow_layout_get_row_height (ClutterFlowLayout *layout, + gfloat *min_height, + gfloat *max_height); + +G_END_DECLS + +#endif /* __CLUTTER_FLOW_LAYOUT_H__ */ diff --git a/clutter/clutter-group.c b/clutter/clutter-group.c index 918aa5ecd..94dfdddf0 100644 --- a/clutter/clutter-group.c +++ b/clutter/clutter-group.c @@ -45,6 +45,7 @@ #include "clutter-group.h" #include "clutter-container.h" +#include "clutter-fixed-layout.h" #include "clutter-main.h" #include "clutter-private.h" #include "clutter-debug.h" @@ -75,6 +76,8 @@ G_DEFINE_TYPE_WITH_CODE (ClutterGroup, struct _ClutterGroupPrivate { GList *children; + + ClutterLayoutManager *layout; }; @@ -126,113 +129,19 @@ clutter_group_pick (ClutterActor *actor, } } -static void -clutter_fixed_layout_get_preferred_width (GList *children, - gfloat *min_width_p, - gfloat *natural_width_p) -{ - GList *l; - gdouble min_right, natural_right; - - /* We will always be at least 0 sized (ie, if all of the actors are - to the left of the origin we won't return a negative size) */ - min_right = 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); - - /* Track the rightmost edge */ - 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 size is defined as the distance from the origin to the - right-hand edge of the rightmost actor */ - if (min_width_p) - *min_width_p = min_right; - - if (natural_width_p) - *natural_width_p = natural_right; -} - -static void -clutter_fixed_layout_get_preferred_height (GList *children, - gfloat *min_height_p, - gfloat *natural_height_p) -{ - GList *l; - gdouble min_bottom, natural_bottom; - - /* We will always be at least 0 sized (ie, if all of the actors are - above the origin we won't return a negative size) */ - min_bottom = 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); - - /* Track the bottommost edge */ - 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 size is defined as the distance from the origin to the bottom - edge of the bottommost actor */ - if (min_height_p) - *min_height_p = min_bottom; - - if (natural_height_p) - *natural_height_p = natural_bottom; -} - -static void -clutter_fixed_layout_allocate (GList *children, - ClutterAllocationFlags flags) -{ - GList *l; - - for (l = children; l != NULL; l = l->next) - { - ClutterActor *child = l->data; - clutter_actor_allocate_preferred_size (child, flags); - } -} - static void clutter_group_get_preferred_width (ClutterActor *self, gfloat for_height, gfloat *min_width_p, gfloat *natural_width_p) { + ClutterContainer *container = CLUTTER_CONTAINER (self); 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); + clutter_layout_manager_get_preferred_width (priv->layout, container, + for_height, + min_width_p, + natural_width_p); } static void @@ -241,12 +150,13 @@ clutter_group_get_preferred_height (ClutterActor *self, gfloat *min_height_p, gfloat *natural_height_p) { + ClutterContainer *container = CLUTTER_CONTAINER (self); 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); + clutter_layout_manager_get_preferred_width (priv->layout, container, + for_width, + min_height_p, + natural_height_p); } static void @@ -254,17 +164,16 @@ clutter_group_allocate (ClutterActor *self, const ClutterActorBox *box, ClutterAllocationFlags flags) { + ClutterContainer *container = CLUTTER_CONTAINER (self); ClutterGroupPrivate *priv = CLUTTER_GROUP (self)->priv; /* chain up to set actor->allocation */ CLUTTER_ACTOR_CLASS (clutter_group_parent_class)->allocate (self, box, flags); - /* 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, flags); + if (priv->children == NULL) + return; + + clutter_layout_manager_allocate (priv->layout, container, box, flags); } static void @@ -281,6 +190,12 @@ clutter_group_dispose (GObject *object) priv->children = NULL; } + if (priv->layout) + { + g_object_unref (priv->layout); + priv->layout = NULL; + } + G_OBJECT_CLASS (clutter_group_parent_class)->dispose (object); } @@ -468,8 +383,10 @@ sort_z_order (gconstpointer a, if (depth_a < depth_b) return -1; + if (depth_a > depth_b) return 1; + return 0; } @@ -521,6 +438,9 @@ 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); } /** diff --git a/clutter/clutter-layout-manager.c b/clutter/clutter-layout-manager.c new file mode 100644 index 000000000..899c10305 --- /dev/null +++ b/clutter/clutter-layout-manager.c @@ -0,0 +1,887 @@ +/* + * Clutter. + * + * An OpenGL based 'interactive canvas' library. + * + * Copyright (C) 2009 Intel Corporation. + * + * 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 . + * + * Author: + * Emmanuele Bassi + */ + +/** + * SECTION:clutter-layout-manager + * @short_description: Layout managers base class + * + * #ClutterLayoutManager is a base abstract class for layout managers. A + * layout manager implements the layouting policy for a composite or a + * container actor: it controls the preferred size of the actor to which + * it has been paired, and it controls the allocation of its children. + * + * Any composite or container #ClutterActor subclass can delegate the + * layouting of its children to a #ClutterLayoutManager. Clutter provides + * a generic container using #ClutterLayoutManager called #ClutterBox. + * + * Clutter provides some simple #ClutterLayoutManager sub-classes, like + * #ClutterFixedLayout and #ClutterBinLayout. + * + * + * Using ClutterLayoutManager inside an Actor + * In order to use a #ClutterLayoutManager inside a #ClutterActor + * sub-class you should invoke clutter_layout_manager_get_preferred_width() + * inside the ClutterActor::get_preferred_width() + * virtual function and clutter_layout_manager_get_preferred_height() + * inside the ClutterActor::get_preferred_height() + * virtual function implementations. You should also call + * clutter_layout_manager_allocate() inside the implementation of the + * ClutterActor::allocate() virtual + * function. + * In order to receive notifications for changes in the layout + * manager policies you should also connect to the + * #ClutterLayoutManager::layout-changed signal and queue a relayout + * on your actor. The following code should be enough if the actor + * does not need to perform specific operations whenever a layout + * manager changes: + * + * g_signal_connect_swapped (layout_manager, + * "layout-changed", + * G_CALLBACK (clutter_actor_queue_relayout), + * actor); + * + * + * + * + * Implementing a ClutterLayoutManager + * The implementation of a layout manager does not differ from + * the implementation of the size requisition and allocation bits of + * #ClutterActor, so you should read the relative documentation + * for subclassing + * ClutterActor. + * The layout manager implementation can hold a back reference + * to the #ClutterContainer by implementing the set_container() + * virtual function. The layout manager should not hold a reference + * on the container actor, to avoid reference cycles. + * If the layout manager has properties affecting the layout + * policies then it should emit the #ClutterLayoutManager::layout-changed + * signal on itself by using the clutter_layout_manager_layout_changed() + * function. + * + * + * #ClutterLayoutManager is available since Clutter 1.2 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "clutter-debug.h" +#include "clutter-layout-manager.h" +#include "clutter-layout-meta.h" +#include "clutter-marshal.h" +#include "clutter-private.h" + +#define LAYOUT_MANAGER_WARN_NOT_IMPLEMENTED(m,method) G_STMT_START { \ + GObject *_obj = G_OBJECT (m); \ + g_warning ("Layout managers of type %s do not implement " \ + "the ClutterLayoutManager::%s method", \ + G_OBJECT_TYPE_NAME (_obj), \ + (method)); } G_STMT_END + +enum +{ + LAYOUT_CHANGED, + + LAST_SIGNAL +}; + +G_DEFINE_ABSTRACT_TYPE (ClutterLayoutManager, + clutter_layout_manager, + G_TYPE_INITIALLY_UNOWNED); + +static GQuark quark_layout_meta = 0; +static guint manager_signals[LAST_SIGNAL] = { 0, }; + +static void +layout_manager_real_get_preferred_width (ClutterLayoutManager *manager, + ClutterContainer *container, + gfloat for_height, + gfloat *min_width_p, + gfloat *nat_width_p) +{ + LAYOUT_MANAGER_WARN_NOT_IMPLEMENTED (manager, "get_preferred_width"); + + if (min_width_p) + *min_width_p = 0.0; + + if (nat_width_p) + *nat_width_p = 0.0; +} + +static void +layout_manager_real_get_preferred_height (ClutterLayoutManager *manager, + ClutterContainer *container, + gfloat for_width, + gfloat *min_height_p, + gfloat *nat_height_p) +{ + LAYOUT_MANAGER_WARN_NOT_IMPLEMENTED (manager, "get_preferred_height"); + + if (min_height_p) + *min_height_p = 0.0; + + if (nat_height_p) + *nat_height_p = 0.0; +} + +static void +layout_manager_real_allocate (ClutterLayoutManager *manager, + ClutterContainer *container, + const ClutterActorBox *allocation, + ClutterAllocationFlags flags) +{ + LAYOUT_MANAGER_WARN_NOT_IMPLEMENTED (manager, "allocate"); +} + +static ClutterLayoutMeta * +layout_manager_real_create_child_meta (ClutterLayoutManager *manager, + ClutterContainer *container, + ClutterActor *actor) +{ + return NULL; +} + +static void +clutter_layout_manager_class_init (ClutterLayoutManagerClass *klass) +{ + quark_layout_meta = + g_quark_from_static_string ("clutter-layout-manager-child-meta"); + + klass->get_preferred_width = layout_manager_real_get_preferred_width; + klass->get_preferred_height = layout_manager_real_get_preferred_height; + klass->allocate = layout_manager_real_allocate; + klass->create_child_meta = layout_manager_real_create_child_meta; + + /** + * ClutterLayoutManager::layout-changed: + * @manager: the #ClutterLayoutManager that emitted the signal + * + * The ::layout-changed signal is emitted each time a layout manager + * has been changed. Every #ClutterActor using the @manager instance + * as a layout manager should connect a handler to the ::layout-changed + * signal and queue a relayout on themselves: + * + * |[ + * static void layout_changed (ClutterLayoutManager *manager, + * ClutterActor *self) + * { + * clutter_actor_queue_relayout (self); + * } + * ... + * self->manager = g_object_ref_sink (manager); + * g_signal_connect (self->manager, "layout-changed", + * G_CALLBACK (layout_changed), + * self); + * ]| + * + * Sub-classes of #ClutterLayoutManager that implement a layout that + * can be controlled or changed using parameters should emit the + * ::layout-changed signal whenever one of the parameters changes, + * by using clutter_layout_manager_layout_changed(). + * + * Since: 1.2 + */ + manager_signals[LAYOUT_CHANGED] = + g_signal_new (I_("layout-changed"), + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ClutterLayoutManagerClass, + layout_changed), + NULL, NULL, + clutter_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +static void +clutter_layout_manager_init (ClutterLayoutManager *manager) +{ +} + +/** + * clutter_layout_manager_get_preferred_width: + * @manager: a #ClutterLayoutManager + * @container: the #ClutterContainer using @manager + * @for_height: the height for which the width should be computed, or -1 + * @min_width_p: (out) (allow-none): return location for the minimum width + * of the layout, or %NULL + * @nat_width_p: (out) (allow-none): return location for the natural width + * of the layout, or %NULL + * + * Computes the minimum and natural widths of the @container according + * to @manager. + * + * See also clutter_actor_get_preferred_width() + * + * Since: 1.2 + */ +void +clutter_layout_manager_get_preferred_width (ClutterLayoutManager *manager, + ClutterContainer *container, + gfloat for_height, + gfloat *min_width_p, + gfloat *nat_width_p) +{ + ClutterLayoutManagerClass *klass; + + g_return_if_fail (CLUTTER_IS_LAYOUT_MANAGER (manager)); + g_return_if_fail (CLUTTER_IS_CONTAINER (container)); + + klass = CLUTTER_LAYOUT_MANAGER_GET_CLASS (manager); + klass->get_preferred_width (manager, container, for_height, + min_width_p, + nat_width_p); +} + +/** + * clutter_layout_manager_get_preferred_height: + * @manager: a #ClutterLayoutManager + * @container: the #ClutterContainer using @manager + * @for_width: the width for which the height should be computed, or -1 + * @min_height_p: (out) (allow-none): return location for the minimum height + * of the layout, or %NULL + * @nat_height_p: (out) (allow-none): return location for the natural height + * of the layout, or %NULL + * + * Computes the minimum and natural heights of the @container according + * to @manager. + * + * See also clutter_actor_get_preferred_height() + * + * Since: 1.2 + */ +void +clutter_layout_manager_get_preferred_height (ClutterLayoutManager *manager, + ClutterContainer *container, + gfloat for_width, + gfloat *min_height_p, + gfloat *nat_height_p) +{ + ClutterLayoutManagerClass *klass; + + g_return_if_fail (CLUTTER_IS_LAYOUT_MANAGER (manager)); + g_return_if_fail (CLUTTER_IS_CONTAINER (container)); + + klass = CLUTTER_LAYOUT_MANAGER_GET_CLASS (manager); + klass->get_preferred_height (manager, container, for_width, + min_height_p, + nat_height_p); +} + +/** + * clutter_layout_manager_allocate: + * @manager: a #ClutterLayoutManager + * @container: the #ClutterContainer using @manager + * @allocation: the #ClutterActorBox containing the allocated area + * of @container + * @flags: the allocation flags + * + * Allocates the children of @container given an area + * + * See also clutter_actor_allocate() + * + * Since: 1.2 + */ +void +clutter_layout_manager_allocate (ClutterLayoutManager *manager, + ClutterContainer *container, + const ClutterActorBox *allocation, + ClutterAllocationFlags flags) +{ + ClutterLayoutManagerClass *klass; + + g_return_if_fail (CLUTTER_IS_LAYOUT_MANAGER (manager)); + g_return_if_fail (CLUTTER_IS_CONTAINER (container)); + g_return_if_fail (allocation != NULL); + + klass = CLUTTER_LAYOUT_MANAGER_GET_CLASS (manager); + klass->allocate (manager, container, allocation, flags); +} + +/** + * clutter_layout_manager_layout_changed: + * @manager: a #ClutterLayoutManager + * + * Emits the #ClutterLayoutManager::layout-changed signal on @manager + * + * This function should only be called by implementations of the + * #ClutterLayoutManager class + * + * Since: 1.2 + */ +void +clutter_layout_manager_layout_changed (ClutterLayoutManager *manager) +{ + g_return_if_fail (CLUTTER_IS_LAYOUT_MANAGER (manager)); + + g_signal_emit (manager, manager_signals[LAYOUT_CHANGED], 0); +} + +/** + * clutter_layout_manager_set_container: + * @manager: a #ClutterLayoutManager + * @container: (allow-none): a #ClutterContainer using @manager + * + * If the #ClutterLayoutManager sub-class allows it, allow + * adding a weak reference of the @container using @manager + * from within the layout manager + * + * The layout manager should not increase the reference + * count of the @container + * + * Since: 1.2 + */ +void +clutter_layout_manager_set_container (ClutterLayoutManager *manager, + ClutterContainer *container) +{ + ClutterLayoutManagerClass *klass; + + g_return_if_fail (CLUTTER_IS_LAYOUT_MANAGER (manager)); + g_return_if_fail (container == NULL || CLUTTER_IS_CONTAINER (container)); + + klass = CLUTTER_LAYOUT_MANAGER_GET_CLASS (manager); + if (klass->set_container) + klass->set_container (manager, container); +} + +static inline ClutterLayoutMeta * +create_child_meta (ClutterLayoutManager *manager, + ClutterContainer *container, + ClutterActor *actor) +{ + ClutterLayoutManagerClass *klass; + + klass = CLUTTER_LAYOUT_MANAGER_GET_CLASS (manager); + + return klass->create_child_meta (manager, container, actor); +} + +static inline ClutterLayoutMeta * +get_child_meta (ClutterLayoutManager *manager, + ClutterContainer *container, + ClutterActor *actor) +{ + ClutterLayoutMeta *layout = NULL; + + layout = g_object_get_qdata (G_OBJECT (actor), quark_layout_meta); + if (layout != NULL) + { + ClutterChildMeta *child = CLUTTER_CHILD_META (layout); + + if (layout->manager == manager && + child->container == container && + child->actor == actor) + return layout; + + /* if the LayoutMeta referenced is not attached to the + * layout manager then we simply ask the layout manager + * to replace it with the right one + */ + layout = create_child_meta (manager, container, actor); + if (layout != NULL) + { + g_assert (CLUTTER_IS_LAYOUT_META (layout)); + g_object_set_qdata_full (G_OBJECT (actor), quark_layout_meta, + layout, + (GDestroyNotify) g_object_unref); + } + + return layout; + } + + return NULL; +} + +/** + * clutter_layout_manager_get_child_meta: + * @manager: a #ClutterLayoutManager + * @container: a #ClutterContainer using @manager + * @actor: a #ClutterActor child of @container + * + * Retrieves the #ClutterLayoutMeta that the layout @manager associated + * to the @actor child of @container + * + * Return value: a #ClutterLayoutMeta or %NULL + * + * Since: 1.0 + */ +ClutterLayoutMeta * +clutter_layout_manager_get_child_meta (ClutterLayoutManager *manager, + ClutterContainer *container, + ClutterActor *actor) +{ + g_return_val_if_fail (CLUTTER_IS_LAYOUT_MANAGER (manager), NULL); + g_return_val_if_fail (CLUTTER_IS_CONTAINER (container), NULL); + g_return_val_if_fail (CLUTTER_IS_ACTOR (actor), NULL); + + return get_child_meta (manager, container, actor); +} + +/** + * clutter_layout_manager_add_child_meta: + * @manager: a #ClutterLayoutManager + * @container: a #ClutterContainer using @manager + * @actor: a #ClutterActor child of @container + * + * Creates and binds a #ClutterLayoutMeta for @manager to + * a child of @container + * + * This function should only be used when implementing containers + * using #ClutterLayoutManager and not by application code + * + * Typically, containers should bind a #ClutterLayoutMeta created + * by a #ClutterLayoutManager when adding a new child, e.g.: + * + * |[ + * static void + * my_container_add (ClutterContainer *container, + * ClutterActor *actor) + * { + * MyContainer *self = MY_CONTAINER (container); + * + * self->children = g_slist_append (self->children, actor); + * clutter_actor_set_parent (actor, CLUTTER_ACTOR (self)); + * + * clutter_layout_manager_add_child_meta (self->layout, + * container, + * actor); + * + * clutter_actor_queue_relayout (CLUTTER_ACTOR (self)); + * + * g_signal_emit_by_name (container, "actor-added"); + * } + * ]| + * + * The #ClutterLayoutMeta should be removed when removing an + * actor; see clutter_layout_manager_remove_child_meta() + * + * Since: 1.2 + */ +void +clutter_layout_manager_add_child_meta (ClutterLayoutManager *manager, + ClutterContainer *container, + ClutterActor *actor) +{ + ClutterLayoutMeta *meta; + + meta = create_child_meta (manager, container, actor); + if (meta == NULL) + return; + + g_object_set_qdata_full (G_OBJECT (actor), quark_layout_meta, + meta, + (GDestroyNotify) g_object_unref); +} + +/** + * clutter_layout_manager_remove_child_meta: + * @manager: a #ClutterLayoutManager + * @container: a #ClutterContainer using @manager + * @actor: a #ClutterActor child of @container + * + * Unbinds and unrefs a #ClutterLayoutMeta for @manager from + * a child of @container + * + * This function should only be used when implementing containers + * using #ClutterLayoutManager and not by application code + * + * Typically, containers should remove a #ClutterLayoutMeta created + * by a #ClutterLayoutManager when removing a child, e.g.: + * + * |[ + * static void + * my_container_remove (ClutterContainer *container, + * ClutterActor *actor) + * { + * MyContainer *self = MY_CONTAINER (container); + * + * g_object_ref (actor); + * + * self->children = g_slist_remove (self->children, actor); + * clutter_actor_unparent (actor); + * + * clutter_layout_manager_remove_child_meta (self->layout, + * container, + * actor); + * + * clutter_actor_queue_relayout (CLUTTER_ACTOR (self)); + * + * g_signal_emit_by_name (container, "actor-removed"); + * + * g_object_unref (actor); + * } + * ]| + * + * See also clutter_layout_manager_add_child_meta() + * + * Since: 1.2 + */ +void +clutter_layout_manager_remove_child_meta (ClutterLayoutManager *manager, + ClutterContainer *container, + ClutterActor *actor) +{ + g_return_if_fail (CLUTTER_IS_LAYOUT_MANAGER (manager)); + g_return_if_fail (CLUTTER_IS_CONTAINER (container)); + g_return_if_fail (CLUTTER_IS_ACTOR (actor)); + + if (get_child_meta (manager, container, actor)) + g_object_set_qdata (G_OBJECT (actor), quark_layout_meta, NULL); +} + +static inline gboolean +layout_set_property_internal (ClutterLayoutManager *manager, + GObject *gobject, + GParamSpec *pspec, + const GValue *value) +{ + if (pspec->flags & G_PARAM_CONSTRUCT_ONLY) + { + g_warning ("%s: Child property '%s' of the layout manager of " + "type '%s' is constructor-only", + G_STRLOC, pspec->name, G_OBJECT_TYPE_NAME (manager)); + return FALSE; + } + + if (!(pspec->flags & G_PARAM_WRITABLE)) + { + g_warning ("%s: Child property '%s' of the layout manager of " + "type '%s' is not writable", + G_STRLOC, pspec->name, G_OBJECT_TYPE_NAME (manager)); + return FALSE; + } + + g_object_set_property (gobject, pspec->name, value); + + return TRUE; +} + +static inline gboolean +layout_get_property_internal (ClutterLayoutManager *manager, + GObject *gobject, + GParamSpec *pspec, + GValue *value) +{ + if (!(pspec->flags & G_PARAM_READABLE)) + { + g_warning ("%s: Child property '%s' of the layout manager of " + "type '%s' is not readable", + G_STRLOC, pspec->name, G_OBJECT_TYPE_NAME (manager)); + return FALSE; + } + + g_object_get_property (gobject, pspec->name, value); + + return TRUE; +} + +/** + * clutter_layout_manager_child_set: + * @manager: a #ClutterLayoutManager + * @container: a #ClutterContainer using @manager + * @actor: a #ClutterActor child of @container + * @first_property: the first property name + * @Varargs: a list of property name and value pairs + * + * Sets a list of properties and their values on the #ClutterLayoutMeta + * associated by @manager to a child of @container + * + * Languages bindings should use clutter_layout_manager_child_set_property() + * instead + * + * Since: 1.2 + */ +void +clutter_layout_manager_child_set (ClutterLayoutManager *manager, + ClutterContainer *container, + ClutterActor *actor, + const gchar *first_property, + ...) +{ + ClutterLayoutMeta *meta; + GObjectClass *klass; + const gchar *pname; + va_list var_args; + + g_return_if_fail (CLUTTER_IS_LAYOUT_MANAGER (manager)); + g_return_if_fail (CLUTTER_IS_CONTAINER (container)); + g_return_if_fail (CLUTTER_IS_ACTOR (actor)); + g_return_if_fail (first_property != NULL); + + meta = get_child_meta (manager, container, actor); + if (meta == NULL) + { + g_warning ("Layout managers of type '%s' do not support " + "child metadata", + g_type_name (G_OBJECT_TYPE (manager))); + return; + } + + klass = G_OBJECT_GET_CLASS (meta); + + va_start (var_args, first_property); + + pname = first_property; + while (pname) + { + GValue value = { 0, }; + GParamSpec *pspec; + gchar *error; + gboolean res; + + pspec = g_object_class_find_property (klass, pname); + if (pspec == NULL) + { + g_warning ("%s: Layout managers of type '%s' have no child " + "property named '%s'", + G_STRLOC, G_OBJECT_TYPE_NAME (manager), pname); + break; + } + + g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (pspec)); + G_VALUE_COLLECT (&value, var_args, 0, &error); + if (error) + { + g_warning ("%s: %s", G_STRLOC, error); + g_free (error); + break; + } + + res = layout_set_property_internal (manager, G_OBJECT (meta), + pspec, + &value); + + g_value_unset (&value); + + if (!res) + break; + + pname = va_arg (var_args, gchar*); + } + + va_end (var_args); +} + +/** + * clutter_layout_manager_child_set_property: + * @manager: a #ClutterLayoutManager + * @container: a #ClutterContainer using @manager + * @actor: a #ClutterActor child of @container + * @property_name: the name of the property to set + * @value: a #GValue with the value of the property to set + * + * Sets a property on the #ClutterLayoutMeta created by @manager and + * attached to a child of @container + * + * Since: 1.2 + */ +void +clutter_layout_manager_child_set_property (ClutterLayoutManager *manager, + ClutterContainer *container, + ClutterActor *actor, + const gchar *property_name, + const GValue *value) +{ + ClutterLayoutMeta *meta; + GObjectClass *klass; + GParamSpec *pspec; + + g_return_if_fail (CLUTTER_IS_LAYOUT_MANAGER (manager)); + g_return_if_fail (CLUTTER_IS_CONTAINER (container)); + g_return_if_fail (CLUTTER_IS_ACTOR (actor)); + g_return_if_fail (property_name != NULL); + g_return_if_fail (value != NULL); + + meta = get_child_meta (manager, container, actor); + if (meta == NULL) + { + g_warning ("Layout managers of type '%s' do not support " + "child metadata", + g_type_name (G_OBJECT_TYPE (manager))); + return; + } + + klass = G_OBJECT_GET_CLASS (meta); + + pspec = g_object_class_find_property (klass, property_name); + if (pspec == NULL) + { + g_warning ("%s: Layout managers of type '%s' have no child " + "property named '%s'", + G_STRLOC, G_OBJECT_TYPE_NAME (manager), property_name); + return; + } + + layout_set_property_internal (manager, G_OBJECT (meta), pspec, value); +} + +/** + * clutter_layout_manager_child_get: + * @manager: a #ClutterLayoutManager + * @container: a #ClutterContainer using @manager + * @actor: a #ClutterActor child of @container + * @first_property: the name of the first property + * @Varargs: a list of property name and return location for the value pairs + * + * Retrieves the values for a list of properties out of the + * #ClutterLayoutMeta created by @manager and attached to the + * child of a @container + * + * Since: 1.2 + */ +void +clutter_layout_manager_child_get (ClutterLayoutManager *manager, + ClutterContainer *container, + ClutterActor *actor, + const gchar *first_property, + ...) +{ + ClutterLayoutMeta *meta; + GObjectClass *klass; + const gchar *pname; + va_list var_args; + + g_return_if_fail (CLUTTER_IS_LAYOUT_MANAGER (manager)); + g_return_if_fail (CLUTTER_IS_CONTAINER (container)); + g_return_if_fail (CLUTTER_IS_ACTOR (actor)); + g_return_if_fail (first_property != NULL); + + meta = get_child_meta (manager, container, actor); + if (meta == NULL) + { + g_warning ("Layout managers of type '%s' do not support " + "child metadata", + g_type_name (G_OBJECT_TYPE (manager))); + return; + } + + klass = G_OBJECT_GET_CLASS (meta); + + va_start (var_args, first_property); + + pname = first_property; + while (pname) + { + GValue value = { 0, }; + GParamSpec *pspec; + gchar *error; + gboolean res; + + pspec = g_object_class_find_property (klass, pname); + if (pspec == NULL) + { + g_warning ("%s: Layout managers of type '%s' have no child " + "property named '%s'", + G_STRLOC, G_OBJECT_TYPE_NAME (manager), pname); + break; + } + + g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (pspec)); + + res = layout_get_property_internal (manager, G_OBJECT (meta), + pspec, + &value); + if (!res) + { + g_value_unset (&value); + break; + } + + G_VALUE_LCOPY (&value, var_args, 0, &error); + if (error) + { + g_warning ("%s: %s", G_STRLOC, error); + g_free (error); + g_value_unset (&value); + break; + } + + g_value_unset (&value); + + pname = va_arg (var_args, gchar*); + } + + va_end (var_args); +} + +/** + * clutter_layout_manager_child_get_property: + * @manager: a #ClutterLayoutManager + * @container: a #ClutterContainer using @manager + * @actor: a #ClutterActor child of @container + * @property_name: the name of the property to get + * @value: a #GValue with the value of the property to get + * + * Gets a property on the #ClutterLayoutMeta created by @manager and + * attached to a child of @container + * + * The #GValue must already be initialized to the type of the property + * and has to be unset with g_value_unset() after extracting the real + * value out of it + * + * Since: 1.2 + */ +void +clutter_layout_manager_child_get_property (ClutterLayoutManager *manager, + ClutterContainer *container, + ClutterActor *actor, + const gchar *property_name, + GValue *value) +{ + ClutterLayoutMeta *meta; + GObjectClass *klass; + GParamSpec *pspec; + + g_return_if_fail (CLUTTER_IS_LAYOUT_MANAGER (manager)); + g_return_if_fail (CLUTTER_IS_CONTAINER (container)); + g_return_if_fail (CLUTTER_IS_ACTOR (actor)); + g_return_if_fail (property_name != NULL); + g_return_if_fail (value != NULL); + + meta = get_child_meta (manager, container, actor); + if (meta == NULL) + { + g_warning ("Layout managers of type %s do not support " + "child metadata", + g_type_name (G_OBJECT_TYPE (manager))); + return; + } + + klass = G_OBJECT_GET_CLASS (meta); + + pspec = g_object_class_find_property (klass, property_name); + if (pspec == NULL) + { + g_warning ("%s: Layout managers of type '%s' have no child " + "property named '%s'", + G_STRLOC, G_OBJECT_TYPE_NAME (manager), property_name); + return; + } + + layout_get_property_internal (manager, G_OBJECT (meta), pspec, value); +} diff --git a/clutter/clutter-layout-manager.h b/clutter/clutter-layout-manager.h new file mode 100644 index 000000000..d3333c7cb --- /dev/null +++ b/clutter/clutter-layout-manager.h @@ -0,0 +1,187 @@ +/* + * Clutter. + * + * An OpenGL based 'interactive canvas' library. + * + * Copyright (C) 2009 Intel Corporation. + * + * 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 . + * + * Author: + * Emmanuele Bassi + */ + +#if !defined(__CLUTTER_H_INSIDE__) && !defined(CLUTTER_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __CLUTTER_LAYOUT_MANAGER_H__ +#define __CLUTTER_LAYOUT_MANAGER_H__ + +#include +#include +#include + +G_BEGIN_DECLS + +#define CLUTTER_TYPE_LAYOUT_MANAGER (clutter_layout_manager_get_type ()) +#define CLUTTER_LAYOUT_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CLUTTER_TYPE_LAYOUT_MANAGER, ClutterLayoutManager)) +#define CLUTTER_IS_LAYOUT_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CLUTTER_TYPE_LAYOUT_MANAGER)) +#define CLUTTER_LAYOUT_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CLUTTER_TYPE_LAYOUT_MANAGER, ClutterLayoutManagerClass)) +#define CLUTTER_IS_LAYOUT_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CLUTTER_TYPE_LAYOUT_MANAGER)) +#define CLUTTER_LAYOUT_MANAGER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CLUTTER_TYPE_LAYOUT_MANAGER, ClutterLayoutManagerClass)) + +typedef struct _ClutterLayoutManager ClutterLayoutManager; +typedef struct _ClutterLayoutManagerClass ClutterLayoutManagerClass; + +/** + * ClutterLayoutManager: + * + * The #ClutterLayoutManager structure contains only private data + * and should be accessed using the provided API + * + * Since: 1.2 + */ +struct _ClutterLayoutManager +{ + /*< private >*/ + GInitiallyUnowned parent_instance; + + /* padding for future expansion */ + gpointer dummy; +}; + +/** + * ClutterLayoutManagerClass: + * @get_preferred_width: virtual function; override to provide a preferred + * width for the layout manager. See also the get_preferred_width() + * virtual function in #ClutterActor + * @get_preferred_height: virtual function; override to provide a preferred + * height for the layout manager. See also the get_preferred_height() + * virtual function in #ClutterActor + * @allocate: virtual function; override to allocate the children of the + * layout manager. See also the allocate() virtual function in + * #ClutterActor + * @set_container: virtual function; override to set a back pointer + * on the #ClutterContainer using the layout manager. The implementation + * should not take a reference on the container, but just take a weak + * reference, to avoid potential leaks due to reference cycles + * @create_child_meta: virtual function; override to create a + * #ClutterChildMeta instance associated to a #ClutterContainer and a + * child #ClutterActor, used to maintain layout manager specific properties + * @layout_changed: class handler for the #ClutterLayoutManager::layout-changed + * signal + * + * The #ClutterLayoutManagerClass structure contains only private + * data and should be accessed using the provided API + * + * Since: 1.2 + */ +struct _ClutterLayoutManagerClass +{ + /*< private >*/ + GInitiallyUnownedClass parent_class; + + /*< public >*/ + void (* get_preferred_width) (ClutterLayoutManager *manager, + ClutterContainer *container, + gfloat for_height, + gfloat *minimum_width_p, + gfloat *natural_width_p); + void (* get_preferred_height) (ClutterLayoutManager *manager, + ClutterContainer *container, + gfloat for_width, + gfloat *minimum_height_p, + gfloat *natural_height_p); + void (* allocate) (ClutterLayoutManager *manager, + ClutterContainer *container, + const ClutterActorBox *allocation, + ClutterAllocationFlags flags); + + void (* set_container) (ClutterLayoutManager *manager, + ClutterContainer *container); + + ClutterLayoutMeta *(* create_child_meta) (ClutterLayoutManager *manager, + ClutterContainer *container, + ClutterActor *actor); + + void (* layout_changed) (ClutterLayoutManager *manager); + + /*< private >*/ + /* padding for future expansion */ + void (* _clutter_padding_1) (void); + void (* _clutter_padding_2) (void); + void (* _clutter_padding_3) (void); + void (* _clutter_padding_4) (void); + void (* _clutter_padding_5) (void); + void (* _clutter_padding_6) (void); + void (* _clutter_padding_7) (void); + void (* _clutter_padding_8) (void); +}; + +GType clutter_layout_manager_get_type (void) G_GNUC_CONST; + +void clutter_layout_manager_get_preferred_width (ClutterLayoutManager *manager, + ClutterContainer *container, + gfloat for_height, + gfloat *min_width_p, + gfloat *nat_width_p); +void clutter_layout_manager_get_preferred_height (ClutterLayoutManager *manager, + ClutterContainer *container, + gfloat for_width, + gfloat *min_height_p, + gfloat *nat_height_p); +void clutter_layout_manager_allocate (ClutterLayoutManager *manager, + ClutterContainer *container, + const ClutterActorBox *allocation, + ClutterAllocationFlags flags); + +void clutter_layout_manager_set_container (ClutterLayoutManager *manager, + ClutterContainer *container); +void clutter_layout_manager_layout_changed (ClutterLayoutManager *manager); + +ClutterLayoutMeta *clutter_layout_manager_get_child_meta (ClutterLayoutManager *manager, + ClutterContainer *container, + ClutterActor *actor); +void clutter_layout_manager_add_child_meta (ClutterLayoutManager *manager, + ClutterContainer *container, + ClutterActor *actor); +void clutter_layout_manager_remove_child_meta (ClutterLayoutManager *manager, + ClutterContainer *container, + ClutterActor *actor); + +void clutter_layout_manager_child_set (ClutterLayoutManager *manager, + ClutterContainer *container, + ClutterActor *actor, + const gchar *first_property, + ...) G_GNUC_NULL_TERMINATED; +void clutter_layout_manager_child_get (ClutterLayoutManager *manager, + ClutterContainer *container, + ClutterActor *actor, + const gchar *first_property, + ...) G_GNUC_NULL_TERMINATED; +void clutter_layout_manager_child_set_property (ClutterLayoutManager *manager, + ClutterContainer *container, + ClutterActor *actor, + const gchar *property_name, + const GValue *value); +void clutter_layout_manager_child_get_property (ClutterLayoutManager *manager, + ClutterContainer *container, + ClutterActor *actor, + const gchar *property_name, + GValue *value); + +G_END_DECLS + +#endif /* __CLUTTER_LAYOUT_MANAGER_H__ */ diff --git a/clutter/clutter-layout-meta.c b/clutter/clutter-layout-meta.c new file mode 100644 index 000000000..f7bf423f0 --- /dev/null +++ b/clutter/clutter-layout-meta.c @@ -0,0 +1,142 @@ +/* + * Clutter. + * + * An OpenGL based 'interactive canvas' library. + * + * Copyright (C) 2009 Intel Corporation. + * + * 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 . + * + * Author: + * Emmanuele Bassi + */ + +/** + * SECTION:clutter-layout-meta + * @short_description: Wrapper for actors inside a layout manager + * + * #ClutterLayoutMeta is a wrapper object created by #ClutterLayoutManager + * implementations in order to store child-specific data and properties. + * + * A #ClutterLayoutMeta wraps a #ClutterActor inside a #ClutterContainer + * using a #ClutterLayoutManager. + * + * #ClutterLayoutMeta is available since Clutter 1.2 + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "clutter-layout-meta.h" +#include "clutter-debug.h" +#include "clutter-private.h" + +G_DEFINE_ABSTRACT_TYPE (ClutterLayoutMeta, + clutter_layout_meta, + CLUTTER_TYPE_CHILD_META); + +enum +{ + PROP_0, + + PROP_MANAGER +}; + +static void +clutter_layout_meta_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + ClutterLayoutMeta *layout_meta = CLUTTER_LAYOUT_META (object); + + switch (prop_id) + { + case PROP_MANAGER: + layout_meta->manager = g_value_get_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +clutter_layout_meta_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + ClutterLayoutMeta *layout_meta = CLUTTER_LAYOUT_META (object); + + switch (prop_id) + { + case PROP_MANAGER: + g_value_set_object (value, layout_meta->manager); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +clutter_layout_meta_class_init (ClutterLayoutMetaClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GParamSpec *pspec; + + gobject_class->set_property = clutter_layout_meta_set_property; + gobject_class->get_property = clutter_layout_meta_get_property; + + /** + * ClutterLayoutMeta:manager: + * + * The #ClutterLayoutManager that created this #ClutterLayoutMeta. + * + * Since: 1.2 + */ + pspec = g_param_spec_object ("manager", + "Manager", + "The manager that created this data", + CLUTTER_TYPE_LAYOUT_MANAGER, + G_PARAM_CONSTRUCT_ONLY | + CLUTTER_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_MANAGER, pspec); +} + +static void +clutter_layout_meta_init (ClutterLayoutMeta *self) +{ +} + +/** + * clutter_layout_meta_get_manager: + * @data: a #ClutterLayoutMeta + * + * Retrieves the actor wrapped by @data + * + * Return value: (transfer none): a #ClutterLayoutManager + * + * Since: 1.2 + */ +ClutterLayoutManager * +clutter_layout_meta_get_manager (ClutterLayoutMeta *data) +{ + g_return_val_if_fail (CLUTTER_IS_LAYOUT_META (data), NULL); + + return data->manager; +} diff --git a/clutter/clutter-layout-meta.h b/clutter/clutter-layout-meta.h new file mode 100644 index 000000000..ac34daeea --- /dev/null +++ b/clutter/clutter-layout-meta.h @@ -0,0 +1,85 @@ +/* + * Clutter. + * + * An OpenGL based 'interactive canvas' library. + * + * Copyright (C) 2009 Intel Corporation + * + * 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 . + * + * Author: + * Emmanuele Bassi + */ + +#if !defined(__CLUTTER_H_INSIDE__) && !defined(CLUTTER_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __CLUTTER_LAYOUT_META_H__ +#define __CLUTTER_LAYOUT_META_H__ + +#include +#include +#include + +G_BEGIN_DECLS + +#define CLUTTER_TYPE_LAYOUT_META (clutter_layout_meta_get_type ()) +#define CLUTTER_LAYOUT_META(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CLUTTER_TYPE_LAYOUT_META, ClutterLayoutMeta)) +#define CLUTTER_IS_LAYOUT_META(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CLUTTER_TYPE_LAYOUT_META)) +#define CLUTTER_LAYOUT_META_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CLUTTER_TYPE_LAYOUT_META, ClutterLayoutMetaClass)) +#define CLUTTER_IS_LAYOUT_META_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CLUTTER_TYPE_LAYOUT_META)) +#define CLUTTER_LAYOUT_META_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CLUTTER_TYPE_LAYOUT_META, ClutterLayoutMetaClass)) + +/* ClutterLayoutMeta is defined in clutter-types.h */ + +typedef struct _ClutterLayoutMetaClass ClutterLayoutMetaClass; + +/** + * ClutterLayoutMeta + * @manager: the layout manager handling this data + * + * Sub-class of #ClutterChildMeta specific for layout managers + * + * A #ClutterLayoutManager sub-class should create a #ClutterLayoutMeta + * instance by overriding the #ClutterLayoutManager::create_child_meta() + * virtual function + * + * Since: 1.2 + */ +struct _ClutterLayoutMeta +{ + /*< private >*/ + ClutterChildMeta parent_instance; + + /*< public >*/ + ClutterLayoutManager *manager; + + /*< private >*/ + /* padding */ + gpointer dummy; +}; + +struct _ClutterLayoutMetaClass +{ + ClutterChildMetaClass parent_class; +}; + +GType clutter_layout_meta_get_type (void) G_GNUC_CONST; + +ClutterLayoutManager *clutter_layout_meta_get_manager (ClutterLayoutMeta *data); + +G_END_DECLS + +#endif /* __CLUTTER_LAYOUT_META_H__ */ diff --git a/clutter/clutter-types.h b/clutter/clutter-types.h index 69d6aa129..13f120307 100644 --- a/clutter/clutter-types.h +++ b/clutter/clutter-types.h @@ -43,6 +43,7 @@ typedef struct _ClutterActor ClutterActor; typedef struct _ClutterStage ClutterStage; typedef struct _ClutterContainer ClutterContainer; /* dummy */ typedef struct _ClutterChildMeta ClutterChildMeta; +typedef struct _ClutterLayoutMeta ClutterLayoutMeta; /** * ClutterGravity: diff --git a/clutter/clutter.h b/clutter/clutter.h index 701cf3176..087d93d40 100644 --- a/clutter/clutter.h +++ b/clutter/clutter.h @@ -43,6 +43,9 @@ #include "clutter-behaviour-rotate.h" #include "clutter-behaviour-scale.h" #include "clutter-binding-pool.h" +#include "clutter-bin-layout.h" +#include "clutter-box.h" +#include "clutter-box-layout.h" #include "clutter-cairo-texture.h" #include "clutter-child-meta.h" #include "clutter-clone.h" @@ -50,10 +53,14 @@ #include "clutter-container.h" #include "clutter-event.h" #include "clutter-feature.h" +#include "clutter-fixed-layout.h" +#include "clutter-flow-layout.h" #include "clutter-frame-source.h" #include "clutter-group.h" #include "clutter-interval.h" #include "clutter-keysyms.h" +#include "clutter-layout-manager.h" +#include "clutter-layout-meta.h" #include "clutter-list-model.h" #include "clutter-main.h" #include "clutter-media.h" diff --git a/doc/reference/clutter/clutter-docs.xml.in b/doc/reference/clutter/clutter-docs.xml.in index 1a6409105..d00ec1d5c 100644 --- a/doc/reference/clutter/clutter-docs.xml.in +++ b/doc/reference/clutter/clutter-docs.xml.in @@ -55,6 +55,8 @@ + + @@ -71,6 +73,16 @@ + + + + + Layout managers + + + + + @@ -198,13 +210,13 @@ Object Hierarchy - + Object Index - + diff --git a/doc/reference/clutter/clutter-sections.txt b/doc/reference/clutter/clutter-sections.txt index ca9afbee5..36dc187e3 100644 --- a/doc/reference/clutter/clutter-sections.txt +++ b/doc/reference/clutter/clutter-sections.txt @@ -305,6 +305,8 @@ clutter_actor_get_preferred_width clutter_actor_get_preferred_height clutter_actor_set_fixed_position_set clutter_actor_get_fixed_position_set +clutter_actor_set_request_mode +clutter_actor_get_request_mode clutter_actor_set_geometry @@ -1726,3 +1728,195 @@ CLUTTER_STAGE_MANAGER_GET_CLASS clutter_stage_manager_get_type + +
+Layout Managers +clutter-layout-manager +ClutterLayoutManager +ClutterLayoutManagerClass +clutter_layout_manager_get_preferred_width +clutter_layout_manager_get_preferred_height +clutter_layout_manager_allocate +clutter_layout_manager_layout_changed +clutter_layout_manager_set_container + + +clutter_layout_manager_add_child_meta +clutter_layout_manager_remove_child_meta +clutter_layout_manager_get_child_meta +clutter_layout_manager_child_set +clutter_layout_manager_child_set_property +clutter_layout_manager_child_get +clutter_layout_manager_child_get_property + + +CLUTTER_TYPE_LAYOUT_MANAGER +CLUTTER_LAYOUT_MANAGER +CLUTTER_LAYOUT_MANAGER_CLASS +CLUTTER_IS_LAYOUT_MANAGER +CLUTTER_IS_LAYOUT_MANAGER_CLASS +CLUTTER_LAYOUT_MANAGER_GET_CLASS + + +clutter_layout_manager_get_type +
+ +
+ClutterFixedLayout +clutter-fixed-layout +ClutterFixedLayout +ClutterFixedLayoutClass +clutter_fixed_layout_new + + +CLUTTER_TYPE_FIXED_LAYOUT +CLUTTER_FIXED_LAYOUT +CLUTTER_FIXED_LAYOUT_CLASS +CLUTTER_IS_FIXED_LAYOUT +CLUTTER_IS_FIXED_LAYOUT_CLASS +CLUTTER_FIXED_LAYOUT_GET_CLASS + + +clutter_fixed_layout_get_type +
+ +
+ClutterBinLayout +clutter-bin-layout +ClutterBinAlignment +ClutterBinLayout +ClutterBinLayoutClass +clutter_bin_layout_new +clutter_bin_layout_set_alignment +clutter_bin_layout_get_alignment +clutter_bin_layout_add + + +CLUTTER_TYPE_BIN_LAYOUT +CLUTTER_BIN_LAYOUT +CLUTTER_BIN_LAYOUT_CLASS +CLUTTER_IS_BIN_LAYOUT +CLUTTER_IS_BIN_LAYOUT_CLASS +CLUTTER_BIN_LAYOUT_GET_CLASS + + +ClutterBinLayoutPrivate +clutter_bin_layout_get_type +
+ +
+ClutterBox +clutter-box +ClutterBox +ClutterBoxClass +clutter_box_new +clutter_box_set_layout_manager +clutter_box_get_layout_manager +clutter_box_set_color +clutter_box_get_color + + +clutter_box_pack +clutter_box_packv + + +CLUTTER_TYPE_BOX +CLUTTER_BOX +CLUTTER_BOX_CLASS +CLUTTER_IS_BOX +CLUTTER_IS_BOX_CLASS +CLUTTER_BOX_GET_CLASS + + +ClutterBoxPrivate +clutter_box_get_type +
+ +
+ClutterLayoutMeta +clutter-layout-meta +ClutterLayoutMeta +ClutterLayoutMetaClass +clutter_layout_meta_get_manager + + +CLUTTER_TYPE_LAYOUT_META +CLUTTER_LAYOUT_META +CLUTTER_LAYOUT_META_CLASS +CLUTTER_IS_LAYOUT_META +CLUTTER_IS_LAYOUT_META_CLASS +CLUTTER_LAYOUT_META_GET_CLASS + + +clutter_layout_meta_get_type +
+ +
+clutter-flow-layout +ClutterFlowLayout +ClutterFlowOrientation +ClutterFlowLayout +ClutterFlowLayoutClass +clutter_flow_layout_new +clutter_flow_layout_set_orientation +clutter_flow_layout_get_orientation + + +clutter_flow_layout_set_column_spacing +clutter_flow_layout_get_column_spacing +clutter_flow_layout_set_row_spacing +clutter_flow_layout_get_row_spacing +clutter_flow_layout_set_column_width +clutter_flow_layout_get_column_width +clutter_flow_layout_set_row_height +clutter_flow_layout_get_row_height + + +CLUTTER_TYPE_FLOW_LAYOUT +CLUTTER_FLOW_LAYOUT +CLUTTER_FLOW_LAYOUT_CLASS +CLUTTER_IS_FLOW_LAYOUT +CLUTTER_IS_FLOW_LAYOUT_CLASS +CLUTTER_FLOW_LAYOUT_GET_CLASS + +ClutterFlowLayoutPrivate +clutter_flow_layout_get_type +
+ +
+ClutterBoxLayout +clutter-box-layout +ClutterBoxAlignment +ClutterBoxLayout +ClutterBoxLayoutClass +clutter_box_layout_new +clutter_box_layout_set_pack_start +clutter_box_layout_get_pack_start +clutter_box_layout_set_spacing +clutter_box_layout_get_spacing +clutter_box_layout_set_vertical +clutter_box_layout_get_vertical + + +clutter_box_layout_pack + + +clutter_box_layout_set_alignment +clutter_box_layout_get_alignment +clutter_box_layout_set_expand +clutter_box_layout_get_expand +clutter_box_layout_set_fill +clutter_box_layout_get_fill + + +CLUTTER_TYPE_BOX_LAYOUT +CLUTTER_BOX_LAYOUT +CLUTTER_BOX_LAYOUT_CLASS +CLUTTER_IS_BOX_LAYOUT +CLUTTER_IS_BOX_LAYOUT_CLASS +CLUTTER_BOX_LAYOUT_GET_CLASS + + +ClutterBoxLayoutPrivate +clutter_box_layout_get_type +
diff --git a/doc/reference/clutter/clutter.types b/doc/reference/clutter/clutter.types index 5a243947f..9c62cb757 100644 --- a/doc/reference/clutter/clutter.types +++ b/doc/reference/clutter/clutter.types @@ -33,3 +33,10 @@ clutter_animation_get_type clutter_interval_get_type clutter_stage_manager_get_type clutter_binding_pool_get_type +clutter_box_get_type +clutter_layout_manager_get_type +clutter_layout_meta_get_type +clutter_fixed_layout_get_type +clutter_bin_layout_get_type +clutter_flow_layout_get_type +clutter_box_layout_get_type diff --git a/tests/interactive/Makefile.am b/tests/interactive/Makefile.am index ae8c3e7d9..4693ef002 100644 --- a/tests/interactive/Makefile.am +++ b/tests/interactive/Makefile.am @@ -43,7 +43,10 @@ UNIT_TESTS = \ test-text.c \ test-text-field.c \ test-clutter-cairo-flowers.c \ - test-cogl-vertex-buffer.c + test-cogl-vertex-buffer.c \ + test-bin-layout.c \ + test-flow-layout.c \ + test-box-layout.c if X11_TESTS UNIT_TESTS += test-pixmap.c diff --git a/tests/interactive/test-bin-layout.c b/tests/interactive/test-bin-layout.c new file mode 100644 index 000000000..8fcc0515c --- /dev/null +++ b/tests/interactive/test-bin-layout.c @@ -0,0 +1,185 @@ +#include +#include +#include +#include + +static ClutterActor * +make_background (const ClutterColor *color, + gfloat width, + gfloat height) +{ + ClutterActor *tex = clutter_cairo_texture_new (width, height); + cairo_t *cr = clutter_cairo_texture_create (CLUTTER_CAIRO_TEXTURE (tex)); + cairo_pattern_t *pat; + gfloat x, y; + +#define BG_ROUND_RADIUS 12 + + x = y = 0; + + cairo_move_to (cr, BG_ROUND_RADIUS, y); + cairo_line_to (cr, width - BG_ROUND_RADIUS, y); + cairo_curve_to (cr, width, y, width, y, width, BG_ROUND_RADIUS); + cairo_line_to (cr, width, height - BG_ROUND_RADIUS); + cairo_curve_to (cr, width, height, width, height, width - BG_ROUND_RADIUS, height); + cairo_line_to (cr, BG_ROUND_RADIUS, height); + cairo_curve_to (cr, x, height, x, height, x, height - BG_ROUND_RADIUS); + cairo_line_to (cr, x, BG_ROUND_RADIUS); + cairo_curve_to (cr, x, y, x, y, BG_ROUND_RADIUS, y); + + cairo_close_path (cr); + + clutter_cairo_set_source_color (cr, color); + cairo_stroke (cr); + + x += 4; + y += 4; + width -= 4; + height -= 4; + + cairo_move_to (cr, BG_ROUND_RADIUS, y); + cairo_line_to (cr, width - BG_ROUND_RADIUS, y); + cairo_curve_to (cr, width, y, width, y, width, BG_ROUND_RADIUS); + cairo_line_to (cr, width, height - BG_ROUND_RADIUS); + cairo_curve_to (cr, width, height, width, height, width - BG_ROUND_RADIUS, height); + cairo_line_to (cr, BG_ROUND_RADIUS, height); + cairo_curve_to (cr, x, height, x, height, x, height - BG_ROUND_RADIUS); + cairo_line_to (cr, x, BG_ROUND_RADIUS); + cairo_curve_to (cr, x, y, x, y, BG_ROUND_RADIUS, y); + + cairo_close_path (cr); + + pat = cairo_pattern_create_linear (0, 0, 0, height); + cairo_pattern_add_color_stop_rgba (pat, 1, .85, .85, .85, 1); + cairo_pattern_add_color_stop_rgba (pat, .95, 1, 1, 1, 1); + cairo_pattern_add_color_stop_rgba (pat, .05, 1, 1, 1, 1); + cairo_pattern_add_color_stop_rgba (pat, 0, .85, .85, .85, 1); + + cairo_set_source (cr, pat); + cairo_fill (cr); + + cairo_pattern_destroy (pat); + cairo_destroy (cr); + +#undef BG_ROUND_RADIUS + + return tex; +} + +static gboolean +on_box_enter (ClutterActor *box, + ClutterEvent *event, + ClutterActor *emblem) +{ + clutter_actor_animate (emblem, CLUTTER_LINEAR, 150, + "opacity", 255, + NULL); + + return TRUE; +} + +static gboolean +on_box_leave (ClutterActor *box, + ClutterEvent *event, + ClutterActor *emblem) +{ + clutter_actor_animate (emblem, CLUTTER_LINEAR, 150, + "opacity", 0, + NULL); + + return TRUE; +} + +G_MODULE_EXPORT int +test_bin_layout_main (int argc, char *argv[]) +{ + ClutterActor *stage, *box, *rect; + ClutterLayoutManager *layout; + ClutterColor stage_color = { 0xe0, 0xf2, 0xfc, 0xff }; + ClutterColor bg_color = { 0xcc, 0xcc, 0xcc, 0x99 }; + ClutterColor *color; + + clutter_init (&argc, &argv); + + stage = clutter_stage_get_default (); + clutter_stage_set_title (CLUTTER_STAGE (stage), "Box test"); + clutter_stage_set_color (CLUTTER_STAGE (stage), &stage_color); + clutter_actor_set_size (stage, 640, 480); + + layout = clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_CENTER, + CLUTTER_BIN_ALIGNMENT_CENTER); + + box = clutter_box_new (layout); + clutter_container_add_actor (CLUTTER_CONTAINER (stage), box); + clutter_actor_set_anchor_point_from_gravity (box, CLUTTER_GRAVITY_CENTER); + clutter_actor_set_position (box, 320, 240); + clutter_actor_set_reactive (box, TRUE); + clutter_actor_set_name (box, "box"); + + rect = make_background (&bg_color, 200, 200); + + /* first method: use clutter_box_pack() */ + clutter_box_pack (CLUTTER_BOX (box), rect, + "x-align", CLUTTER_BIN_ALIGNMENT_FILL, + "y-align", CLUTTER_BIN_ALIGNMENT_FILL, + NULL); + + clutter_actor_lower_bottom (rect); + clutter_actor_set_name (rect, "background"); + + + { + ClutterActor *tex; + GError *error; + + error = NULL; + tex = clutter_texture_new_from_file ("redhand.png", &error); + if (error) + g_error ("Unable to create texture: %s", error->message); + + clutter_texture_set_keep_aspect_ratio (CLUTTER_TEXTURE (tex), TRUE); + + /* second method: use clutter_bin_layout_add() */ + clutter_bin_layout_add (CLUTTER_BIN_LAYOUT (layout), tex, + CLUTTER_BIN_ALIGNMENT_CENTER, + CLUTTER_BIN_ALIGNMENT_CENTER); + + clutter_actor_raise (tex, rect); + clutter_actor_set_width (tex, 175); + clutter_actor_set_name (tex, "texture"); + } + + color = clutter_color_new (g_random_int_range (0, 255), + g_random_int_range (0, 255), + g_random_int_range (0, 255), + 224); + + rect = clutter_rectangle_new_with_color (color); + + /* third method: container_add() and set_alignment() */ + clutter_container_add_actor (CLUTTER_CONTAINER (box), rect); + clutter_bin_layout_set_alignment (CLUTTER_BIN_LAYOUT (layout), rect, + CLUTTER_BIN_ALIGNMENT_END, + CLUTTER_BIN_ALIGNMENT_END); + + clutter_actor_set_size (rect, 50, 50); + clutter_actor_set_opacity (rect, 0); + clutter_actor_raise_top (rect); + clutter_actor_set_name (rect, "emblem"); + + + g_signal_connect (box, + "enter-event", G_CALLBACK (on_box_enter), + rect); + g_signal_connect (box, + "leave-event", G_CALLBACK (on_box_leave), + rect); + + clutter_actor_show_all (stage); + + clutter_main (); + + clutter_color_free (color); + + return EXIT_SUCCESS; +} diff --git a/tests/interactive/test-box-layout.c b/tests/interactive/test-box-layout.c new file mode 100644 index 000000000..9903fcfa9 --- /dev/null +++ b/tests/interactive/test-box-layout.c @@ -0,0 +1,207 @@ +/* + * Copyright 2009 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU Lesser General Public License, + * version 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * Boston, MA 02111-1307, USA. + * + */ +#include +#include + +#include + +#include + +static ClutterActor *hover_actor = NULL; + +static void +enter_event (ClutterActor *actor, + ClutterEvent *event, + gpointer data) +{ + ClutterColor color = { 0x00, 0x00, 0x00, 0xff }; + + clutter_rectangle_set_border_width (CLUTTER_RECTANGLE (actor), 2); + clutter_rectangle_set_border_color (CLUTTER_RECTANGLE (actor), &color); + + hover_actor = actor; +} + +static void +leave_event (ClutterActor *actor, + ClutterEvent *event, + gpointer data) +{ + clutter_rectangle_set_border_width (CLUTTER_RECTANGLE (actor), 0); + + hover_actor = NULL; +} + +static gboolean +button_release_event (ClutterActor *actor, + ClutterEvent *event, + ClutterBoxLayout *box) +{ + gboolean xfill, yfill; + ClutterBoxAlignment xalign, yalign; + gint button; + + button = clutter_event_get_button (event); + + if (button == 1) + { + clutter_box_layout_get_fill (box, actor, &xfill, &yfill); + clutter_box_layout_set_fill (box, actor, + xfill ? FALSE : TRUE, + yfill ? FALSE : TRUE); + } + else + { + clutter_box_layout_get_alignment (box, actor, &xalign, &yalign); + + if (xalign < 2) + xalign += 1; + else + xalign = 0; + + if (yalign < 2) + yalign += 1; + else + yalign = 0; + + clutter_box_layout_set_alignment (box, actor, xalign, yalign); + } + + return TRUE; +} + +static void +add_actor (ClutterBoxLayout *box) +{ + ClutterActor *rect; + ClutterColor color = { 0xff, 0xff, 0xff, 255 }; + static gboolean expand = TRUE; + + clutter_color_from_hls (&color, + g_random_double_range (0.0, 360.0), + 0.5, + 0.5); + + rect = clutter_rectangle_new_with_color (&color); + clutter_actor_set_size (rect, 32, 64); + clutter_box_layout_pack (box, rect, expand, + FALSE, /* x-fill */ + FALSE, /* y-fill */ + CLUTTER_BOX_ALIGNMENT_CENTER, + CLUTTER_BOX_ALIGNMENT_CENTER); + + clutter_actor_set_reactive (rect, TRUE); + g_signal_connect (rect, "enter-event", G_CALLBACK (enter_event), NULL); + g_signal_connect (rect, "leave-event", G_CALLBACK (leave_event), NULL); + g_signal_connect (rect, "button-release-event", + G_CALLBACK (button_release_event), + box); + + expand = !expand; +} + +static gboolean +key_release_cb (ClutterActor *actor, + ClutterEvent *event, + ClutterBoxLayout *layout) +{ + gboolean toggle; + guint spacing; + + switch (clutter_event_get_key_symbol (event)) + { + case CLUTTER_v: + toggle = clutter_box_layout_get_vertical (layout); + clutter_box_layout_set_vertical (layout, !toggle); + break; + + case CLUTTER_p: + toggle = clutter_box_layout_get_pack_start (layout); + clutter_box_layout_set_pack_start (layout, !toggle); + break; + + case CLUTTER_s: + spacing = clutter_box_layout_get_spacing (layout); + + if (spacing > 12) + spacing = 0; + else + spacing++; + + clutter_box_layout_set_spacing (layout, spacing); + break; + + case '+': + add_actor (layout); + break; + + default: + return FALSE; + } + + return TRUE; +} + +static void +stage_size_changed_cb (ClutterActor *stage, + const ClutterActorBox *allocation, + ClutterAllocationFlags flags, + ClutterActor *box) +{ + gfloat width, height; + + clutter_actor_box_get_size (allocation, &width, &height); + clutter_actor_set_size (box, width - 100, height - 100); +} + +G_MODULE_EXPORT int +test_box_layout_main (int argc, char *argv[]) +{ + ClutterActor *stage, *box; + ClutterLayoutManager *layout; + gint i; + + clutter_init (&argc, &argv); + + stage = clutter_stage_get_default (); + clutter_stage_set_title (CLUTTER_STAGE (stage), "Box Layout"); + clutter_stage_set_user_resizable (CLUTTER_STAGE (stage), TRUE); + clutter_actor_set_size (stage, 640, 480); + + layout = clutter_box_layout_new (); + + box = clutter_box_new (layout); + clutter_container_add_actor (CLUTTER_CONTAINER (stage), box); + + for (i = 0; i < 5; i++) + add_actor (CLUTTER_BOX_LAYOUT (layout)); + + g_signal_connect (stage, "key-release-event", + G_CALLBACK (key_release_cb), + layout); + g_signal_connect (stage, "allocation-changed", + G_CALLBACK (stage_size_changed_cb), + box); + + clutter_actor_show (stage); + + clutter_main (); + + return EXIT_SUCCESS; +} diff --git a/tests/interactive/test-flow-layout.c b/tests/interactive/test-flow-layout.c new file mode 100644 index 000000000..f8131ee3e --- /dev/null +++ b/tests/interactive/test-flow-layout.c @@ -0,0 +1,171 @@ +#include +#include +#include +#include + +#define N_RECTS 20 + +static gboolean is_homogeneous = FALSE; +static gboolean vertical = FALSE; +static gboolean random_size = FALSE; + +static gint n_rects = N_RECTS; +static gint x_spacing = 0; +static gint y_spacing = 0; + +static GOptionEntry entries[] = { + { + "random-size", 'r', + 0, + G_OPTION_ARG_NONE, + &random_size, + "Randomly size the rectangles", NULL + }, + { + "num-rects", 'n', + 0, + G_OPTION_ARG_INT, + &n_rects, + "Number of rectangles", "RECTS" + }, + { + "vertical", 'v', + 0, + G_OPTION_ARG_NONE, + &vertical, + "Set vertical orientation", NULL + }, + { + "homogeneous", 'h', + 0, + G_OPTION_ARG_NONE, + &is_homogeneous, + "Whether the layout should be homogeneous", NULL + }, + { + "x-spacing", 0, + 0, + G_OPTION_ARG_INT, + &x_spacing, + "Horizontal spacing between elements", "PX" + }, + { + "y-spacing", 0, + 0, + G_OPTION_ARG_INT, + &y_spacing, + "Vertical spacing between elements", "PX" + }, + { NULL } +}; + +static void +on_stage_resize (ClutterActor *stage, + const ClutterActorBox *allocation, + ClutterAllocationFlags flags, + ClutterActor *box) +{ + gfloat width, height; + + clutter_actor_box_get_size (allocation, &width, &height); + + if (vertical) + clutter_actor_set_height (box, height); + else + clutter_actor_set_width (box, width); +} + +G_MODULE_EXPORT int +test_flow_layout_main (int argc, char *argv[]) +{ + ClutterActor *stage, *box; + ClutterLayoutManager *layout; + ClutterColor stage_color = { 0xe0, 0xf2, 0xfc, 0xff }; + ClutterColor box_color = { 255, 255, 255, 255 }; + GError *error; + gint i; + + error = NULL; + clutter_init_with_args (&argc, &argv, + NULL, + entries, + NULL, + &error); + if (error) + { + g_print ("Unable to run test-flow: %s", error->message); + g_error_free (error); + + return EXIT_FAILURE; + } + + stage = clutter_stage_get_default (); + clutter_stage_set_title (CLUTTER_STAGE (stage), "Flow Layout"); + clutter_stage_set_color (CLUTTER_STAGE (stage), &stage_color); + clutter_stage_set_user_resizable (CLUTTER_STAGE (stage), TRUE); + clutter_actor_set_size (stage, 640, 480); + + layout = clutter_flow_layout_new (vertical ? CLUTTER_FLOW_VERTICAL + : CLUTTER_FLOW_HORIZONTAL); + clutter_flow_layout_set_homogeneous (CLUTTER_FLOW_LAYOUT (layout), + is_homogeneous); + clutter_flow_layout_set_column_spacing (CLUTTER_FLOW_LAYOUT (layout), + x_spacing); + clutter_flow_layout_set_row_spacing (CLUTTER_FLOW_LAYOUT (layout), + y_spacing); + + box = clutter_box_new (layout); + clutter_box_set_color (CLUTTER_BOX (box), &box_color); + clutter_container_add_actor (CLUTTER_CONTAINER (stage), box); + clutter_actor_set_position (box, 0, 0); + + if (vertical) + clutter_actor_set_height (box, 480); + else + clutter_actor_set_width (box, 640); + + clutter_actor_set_name (box, "box"); + + for (i = 0; i < n_rects; i++) + { + ClutterColor color = { 255, 255, 255, 224 }; + ClutterActor *rect; + gchar *name; + gfloat width, height; + + name = g_strdup_printf ("rect%02d", i); + + clutter_color_from_hls (&color, + 360.0 / n_rects * i, + 0.5, + 0.8); + rect = clutter_rectangle_new_with_color (&color); + + clutter_container_add_actor (CLUTTER_CONTAINER (box), rect); + + if (random_size) + { + width = g_random_int_range (50, 100); + height = g_random_int_range (50, 100); + } + else + { + width = height = 50; + } + + clutter_actor_set_size (rect, width, height); + clutter_actor_set_name (rect, name); + + g_free (name); + } + + g_signal_connect (stage, + "allocation-changed", G_CALLBACK (on_stage_resize), + box); + + clutter_actor_show_all (stage); + + clutter_main (); + + return EXIT_SUCCESS; +}