From 02b7bf3635ce9a3a67016847e99128bd0a9f67d9 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Tue, 10 Aug 2010 17:43:56 +0100 Subject: [PATCH 1/5] test-flow-layout: Use BindConstraints If we want to fit the FlowLayout to the Stage size we should use the BindConstraint API instead of a watcher on ::allocation-changed. --- tests/interactive/test-flow-layout.c | 28 +++++++--------------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/tests/interactive/test-flow-layout.c b/tests/interactive/test-flow-layout.c index e69170623..b126d3d17 100644 --- a/tests/interactive/test-flow-layout.c +++ b/tests/interactive/test-flow-layout.c @@ -67,22 +67,6 @@ static GOptionEntry entries[] = { { 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[]) { @@ -124,6 +108,13 @@ test_flow_layout_main (int argc, char *argv[]) box = clutter_box_new (layout); clutter_box_set_color (CLUTTER_BOX (box), &box_color); clutter_container_add_actor (CLUTTER_CONTAINER (stage), box); + + if (fit_to_stage) + { + clutter_actor_add_constraint (box, clutter_bind_constraint_new (stage, CLUTTER_BIND_WIDTH, 0.0)); + clutter_actor_add_constraint (box, clutter_bind_constraint_new (stage, CLUTTER_BIND_HEIGHT, 0.0)); + } + clutter_actor_set_position (box, 0, 0); clutter_actor_set_name (box, "box"); @@ -161,11 +152,6 @@ test_flow_layout_main (int argc, char *argv[]) g_free (name); } - if (fit_to_stage) - g_signal_connect (stage, - "allocation-changed", G_CALLBACK (on_stage_resize), - box); - clutter_actor_show_all (stage); clutter_main (); From 07b01888baed28e5fb3f0831a2441218b469f846 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Tue, 10 Aug 2010 17:40:01 +0100 Subject: [PATCH 2/5] actor: Add allocate_align_fill() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Layout managers are using the same code to allocate a child while taking into consideration: • horizontal and vertical alignment • horizontal and vertical fill • the preferred minimum and natural size, depending on the :request-mode property • the text direction for the horizontal alignment • an offset given by the fixed position properties Given the amount of code involved, and the amount of details that can go horribly wrong while copy and pasting such code in various classes - let alone various projects - Clutter should provide an allocate() variant that does the right thing in the right way. This way, we have a single point of failure. --- clutter/clutter-actor.c | 139 +++++++++++++++++++++ clutter/clutter-actor.h | 7 ++ doc/reference/clutter/clutter-sections.txt | 1 + 3 files changed, 147 insertions(+) diff --git a/clutter/clutter-actor.c b/clutter/clutter-actor.c index 645442297..8e6ab132b 100644 --- a/clutter/clutter-actor.c +++ b/clutter/clutter-actor.c @@ -9976,6 +9976,145 @@ clutter_actor_allocate_preferred_size (ClutterActor *self, clutter_actor_allocate (self, &actor_box, flags); } +/** + * clutter_actor_allocate_align_fill: + * @self: a #ClutterActor + * @box: a #ClutterActorBox, containing the available width and height + * @x_align: the horizontal alignment, between 0 and 1 + * @y_align: the vertical alignment, between 0 and 1 + * @x_fill: whether the actor should fill horizontally + * @y_fill: whether the actor should fill vertically + * @flags: allocation flags to be passed to clutter_actor_allocate() + * + * Allocates @self by taking into consideration the available allocation + * area; an alignment factor on either axis; and whether the actor should + * fill the allocation on either axis. + * + * The @box should contain the available allocation width and height; + * if the x1 and y1 members of #ClutterActorBox are not set to 0, the + * allocation will be offset by their value. + * + * This function takes into consideration the geometry request specified by + * the #ClutterActor:request-mode property, and the text direction. + * + * This function is useful for fluid layout managers, like #ClutterBinLayout + * or #ClutterTableLayout + * + * Since: 1.4 + */ +void +clutter_actor_allocate_align_fill (ClutterActor *self, + const ClutterActorBox *box, + gdouble x_align, + gdouble y_align, + gboolean x_fill, + gboolean y_fill, + ClutterAllocationFlags flags) +{ + ClutterActorPrivate *priv; + ClutterActorBox allocation = { 0, }; + gfloat x_offset, y_offset; + gfloat available_width, available_height; + gfloat child_width, child_height; + + g_return_if_fail (CLUTTER_IS_ACTOR (self)); + g_return_if_fail (box != NULL); + g_return_if_fail (x_align >= 0.0 && x_align <= 1.0); + g_return_if_fail (y_align >= 0.0 && y_align <= 1.0); + + priv = self->priv; + + clutter_actor_box_get_origin (box, &x_offset, &y_offset); + clutter_actor_box_get_size (box, &available_width, &available_height); + + if (available_width < 0) + available_width = 0; + + if (available_height < 0) + available_height = 0; + + if (x_fill) + { + allocation.x1 = x_offset; + allocation.x2 = allocation.x1 + available_width; + } + + if (y_fill) + { + allocation.y1 = y_offset; + allocation.y2 = allocation.y1 + available_height; + } + + /* if we are filling horizontally and vertically then we're done */ + if (x_fill && y_fill) + goto out; + + child_width = child_height = 0.0f; + + if (priv->request_mode == CLUTTER_REQUEST_HEIGHT_FOR_WIDTH) + { + gfloat min_width, natural_width; + gfloat min_height, natural_height; + + clutter_actor_get_preferred_width (self, available_height, + &min_width, + &natural_width); + + child_width = CLAMP (natural_width, min_width, available_width); + + if (!y_fill) + { + clutter_actor_get_preferred_height (self, child_width, + &min_height, + &natural_height); + + child_height = CLAMP (natural_height, min_height, available_height); + } + } + else + { + gfloat min_width, natural_width; + gfloat min_height, natural_height; + + clutter_actor_get_preferred_height (self, available_width, + &min_height, + &natural_height); + + child_height = CLAMP (natural_height, min_height, available_height); + + if (!x_fill) + { + clutter_actor_get_preferred_width (self, child_height, + &min_width, + &natural_width); + + child_width = CLAMP (natural_width, min_width, available_width); + } + } + + /* invert the horizontal alignment for RTL languages */ + if (priv->text_direction == CLUTTER_TEXT_DIRECTION_RTL) + x_align = 1.0 - x_align; + + if (!x_fill) + { + allocation.x1 = box->x1 + + ((available_width - child_width) * x_align); + allocation.x2 = allocation.x1 + child_width; + } + + if (!y_fill) + { + allocation.y1 = box->y1 + + ((available_height - child_height) * y_align); + allocation.y2 = allocation.y1 + child_height; + } + +out: + clutter_actor_box_clamp_to_pixel (&allocation); + clutter_actor_allocate (self, &allocation, flags); +} + /** * clutter_actor_grab_key_focus: * @self: a #ClutterActor diff --git a/clutter/clutter-actor.h b/clutter/clutter-actor.h index 518c124e7..c4ede389e 100644 --- a/clutter/clutter-actor.h +++ b/clutter/clutter-actor.h @@ -361,6 +361,13 @@ void clutter_actor_allocate_available_size (ClutterActor gfloat available_width, gfloat available_height, ClutterAllocationFlags flags); +void clutter_actor_allocate_align_fill (ClutterActor *self, + const ClutterActorBox *box, + gdouble x_align, + gdouble y_align, + gboolean x_fill, + gboolean y_fill, + ClutterAllocationFlags flags); void clutter_actor_get_allocation_box (ClutterActor *self, ClutterActorBox *box); void clutter_actor_get_allocation_geometry (ClutterActor *self, diff --git a/doc/reference/clutter/clutter-sections.txt b/doc/reference/clutter/clutter-sections.txt index be64aa1de..d92816525 100644 --- a/doc/reference/clutter/clutter-sections.txt +++ b/doc/reference/clutter/clutter-sections.txt @@ -303,6 +303,7 @@ ClutterAllocationFlags clutter_actor_allocate clutter_actor_allocate_preferred_size clutter_actor_allocate_available_size +clutter_actor_allocate_align_fill clutter_actor_get_allocation_box clutter_actor_get_allocation_geometry clutter_actor_get_allocation_vertices From 6ad4126e1783d1e9a04eaf88accce6d0950064d6 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Tue, 10 Aug 2010 17:43:26 +0100 Subject: [PATCH 3/5] bin-layout: Migrate to allocate_align_fill() Now that we have the code in ClutterActor itself. --- clutter/clutter-bin-layout.c | 100 ++++++----------------------------- 1 file changed, 17 insertions(+), 83 deletions(-) diff --git a/clutter/clutter-bin-layout.c b/clutter/clutter-bin-layout.c index 158485e02..85efa1780 100644 --- a/clutter/clutter-bin-layout.c +++ b/clutter/clutter-bin-layout.c @@ -429,102 +429,36 @@ clutter_bin_layout_allocate (ClutterLayoutManager *manager, ClutterLayoutMeta *meta; ClutterBinLayer *layer; ClutterActorBox child_alloc = { 0, }; - gfloat child_width, child_height; - ClutterRequestMode request; + gdouble x_align, y_align; + gboolean x_fill, y_fill; 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; - } - - child_width = child_height = 0; - 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); - } + child_alloc.x1 = clutter_actor_get_x (child); 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); - } - } + child_alloc.x1 = 0.0f; 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); - } + child_alloc.y1 = clutter_actor_get_y (child); else - { - gdouble y_align = get_bin_alignment_factor (layer->y_align); + child_alloc.y1 = 0.0f; - 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); - } - } + child_alloc.x2 = available_w; + child_alloc.y2 = available_h; - clutter_actor_allocate (child, &child_alloc, flags); + x_fill = (layer->x_align == CLUTTER_BIN_ALIGNMENT_FILL); + y_fill = (layer->y_align == CLUTTER_BIN_ALIGNMENT_FILL); + x_align = get_bin_alignment_factor (layer->x_align); + y_align = get_bin_alignment_factor (layer->y_align); + + clutter_actor_allocate_align_fill (child, &child_alloc, + x_align, y_align, + x_fill, y_fill, + flags); } g_list_free (children); From 1043e77a00b1f5965b9a5ff52db6d0f0bd93e88c Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Tue, 10 Aug 2010 22:20:38 +0100 Subject: [PATCH 4/5] box-layout: Use allocate_align_fill() Remove duplicated code, and use the newly added actor method to compute the child allocation based on alignment and fill. --- clutter/clutter-box-layout.c | 120 +++++++---------------------------- 1 file changed, 23 insertions(+), 97 deletions(-) diff --git a/clutter/clutter-box-layout.c b/clutter/clutter-box-layout.c index 6bafb59be..eaaa0ee1d 100644 --- a/clutter/clutter-box-layout.c +++ b/clutter/clutter-box-layout.c @@ -437,106 +437,22 @@ clutter_box_child_init (ClutterBoxChild *self) self->last_allocation = NULL; } -static inline void -allocate_fill (ClutterActor *child, - ClutterActorBox *childbox, - ClutterBoxChild *box_child) +static gdouble +get_box_alignment_factor (ClutterBoxAlignment alignment) { - 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) + switch (alignment) { - allocation.x1 = childbox->x1; - allocation.x2 = ceilf (allocation.x1 + available_width); + case CLUTTER_BOX_ALIGNMENT_CENTER: + return 0.5; + + case CLUTTER_BOX_ALIGNMENT_START: + return 0.0; + + case CLUTTER_BOX_ALIGNMENT_END: + return 1.0; } - 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; + return 0.0; } static GType @@ -784,7 +700,17 @@ allocate_box_child (ClutterBoxLayout *self, child_box.y2 = floorf (avail_height + 0.5); } - allocate_fill (child, &child_box, box_child); + clutter_actor_allocate_align_fill (child, &child_box, + get_box_alignment_factor (box_child->x_align), + get_box_alignment_factor (box_child->y_align), + box_child->x_fill, + box_child->y_fill, + flags); + + /* retrieve the allocation computed and set by allocate_align_fill(); + * since we call this *after* allocate(), it's just a cheap copy + */ + clutter_actor_get_allocation_box (child, &child_box); if (priv->use_animations && priv->is_animating) { From d14e294d86d35e9128d04ebf9a43367b5babac40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Dapena=20Paz?= Date: Thu, 18 Mar 2010 17:02:31 +0100 Subject: [PATCH 5/5] Add ClutterTableLayout, a layout showing children in rows and columns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A TableLayout is a layout manager that allocates its children in rows and columns. Each child is assigned to a cell (or more if a cell span is set). The supported child properties are: • x-expand and y-expand: if this cell with try to allocate the available extra space for the table. • x-fill and y-fill: if the child will get all the space available in the cell. • x-align and y-align: if the child does not fill the cell, then where the child will be aligned inside the cell. • row-span and col-span: number of cells the child will allocate for itself. Also, the TableLayout has row-spacing and col-spacing for specifying the space in pixels between rows and between columns. We also include a simple test of the layout manager, and the documentation updates. The TableLayout was implemented starting from MxTable and ClutterBoxLayout. http://bugzilla.clutter-project.org/show_bug.cgi?id=2038 Signed-off-by: Emmanuele Bassi --- clutter/Makefile.am | 2 + clutter/clutter-table-layout.c | 2642 ++++++++++++++++++++ clutter/clutter-table-layout.h | 163 ++ clutter/clutter.h | 1 + doc/reference/clutter/clutter-docs.xml.in | 1 + doc/reference/clutter/clutter-sections.txt | 47 + doc/reference/clutter/clutter.types | 1 + tests/interactive/.gitignore | 1 + tests/interactive/Makefile.am | 3 +- tests/interactive/test-table-layout.c | 275 ++ 10 files changed, 3135 insertions(+), 1 deletion(-) create mode 100644 clutter/clutter-table-layout.c create mode 100644 clutter/clutter-table-layout.h create mode 100644 tests/interactive/test-table-layout.c diff --git a/clutter/Makefile.am b/clutter/Makefile.am index a1ef46e3b..37d1f39c5 100644 --- a/clutter/Makefile.am +++ b/clutter/Makefile.am @@ -133,6 +133,7 @@ source_h = \ $(srcdir)/clutter-stage-manager.h \ $(srcdir)/clutter-stage-window.h \ $(srcdir)/clutter-state.h \ + $(srcdir)/clutter-table-layout.h \ $(srcdir)/clutter-texture.h \ $(srcdir)/clutter-text.h \ $(srcdir)/clutter-timeline.h \ @@ -227,6 +228,7 @@ source_c = \ $(srcdir)/clutter-stage-manager.c \ $(srcdir)/clutter-stage-window.c \ $(srcdir)/clutter-state.c \ + $(srcdir)/clutter-table-layout.c \ $(srcdir)/clutter-texture.c \ $(srcdir)/clutter-text.c \ $(srcdir)/clutter-timeline.c \ diff --git a/clutter/clutter-table-layout.c b/clutter/clutter-table-layout.c new file mode 100644 index 000000000..5c8595af4 --- /dev/null +++ b/clutter/clutter-table-layout.c @@ -0,0 +1,2642 @@ +/* + * 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: + * Jose Dapena Paz + * + * Based on the MX MxTable actor by: + * Thomas Wood + * and ClutterBoxLayout by: + * Emmanuele Bassi + */ + +/** + * SECTION:clutter-table-layout + * @title: ClutterTableLayout + * @short_description: A layout manager arranging children in rows + * and columns + * + * The #ClutterTableLayout is a #ClutterLayoutManager implementing the + * following layout policy: + * + * + * children are arranged in a table + * each child specifies the specific row and column + * cell to appear; + * a child can also set a span, and this way, take + * more than one cell both horizontally and vertically; + * 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 horizontal and + * vertical alignment layout properties. + * + * + * It is possible to control the spacing between children of a + * #ClutterTableLayout by using clutter_table_layout_set_row_spacing() + * and clutter_table_layout_set_column_spacing(). + * + * In order to set the layout properties when packing an actor inside a + * #ClutterTableLayout you should use the clutter_table_layout_pack() + * function. + * + * A #ClutterTableLayout can use animations to transition between different + * values of the layout management properties; the easing mode and duration + * used for the animations are controlled by the + * #ClutterTableLayout:easing-mode and #ClutterTableLayout:easing-duration + * properties and their accessor functions. + * + * #ClutterTableLayout is available since Clutter 1.4 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "clutter-table-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_TABLE_CHILD (clutter_table_child_get_type ()) +#define CLUTTER_TABLE_CHILD(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CLUTTER_TYPE_TABLE_CHILD, ClutterTableChild)) +#define CLUTTER_IS_TABLE_CHILD(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CLUTTER_TYPE_TABLE_CHILD)) + +#define CLUTTER_TABLE_LAYOUT_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLUTTER_TYPE_TABLE_LAYOUT, ClutterTableLayoutPrivate)) + +typedef struct _ClutterTableChild ClutterTableChild; +typedef struct _ClutterLayoutMetaClass ClutterTableChildClass; + +typedef struct _DimensionData { + gfloat min_size; + gfloat pref_size; + gfloat final_size; + + guint expand : 1; + guint shrink : 1; + guint visible : 1; +} DimensionData; + +struct _ClutterTableLayoutPrivate +{ + ClutterContainer *container; + + guint col_spacing; + guint row_spacing; + + gint n_rows; + gint n_cols; + gint active_row; + gint active_col; + gint visible_rows; + gint visible_cols; + + GArray *columns; + GArray *rows; + + gulong easing_mode; + guint easing_duration; + + guint is_animating : 1; + guint use_animations : 1; +}; + +struct _ClutterTableChild +{ + ClutterLayoutMeta parent_instance; + + /* the last stable allocation before an animation; it is + * used as the initial ActorBox when interpolating + */ + ClutterActorBox *last_allocation; + + gint col; + gint row; + + gint col_span; + gint row_span; + + ClutterTableAlignment x_align; + ClutterTableAlignment y_align; + + guint x_expand : 1; + guint y_expand : 1; + guint x_fill : 1; + guint y_fill : 1; +}; + +enum +{ + PROP_CHILD_0, + + PROP_CHILD_ROW, + PROP_CHILD_COLUMN, + PROP_CHILD_ROW_SPAN, + PROP_CHILD_COLUMN_SPAN, + PROP_CHILD_X_ALIGN, + PROP_CHILD_Y_ALIGN, + PROP_CHILD_X_FILL, + PROP_CHILD_Y_FILL, + PROP_CHILD_X_EXPAND, + PROP_CHILD_Y_EXPAND +}; + +enum +{ + PROP_0, + + PROP_ROW_SPACING, + PROP_COLUMN_SPACING, + PROP_USE_ANIMATIONS, + PROP_EASING_MODE, + PROP_EASING_DURATION +}; + +G_DEFINE_TYPE (ClutterTableChild, + clutter_table_child, + CLUTTER_TYPE_LAYOUT_META); + +G_DEFINE_TYPE (ClutterTableLayout, + clutter_table_layout, + CLUTTER_TYPE_LAYOUT_MANAGER); + +/* + * ClutterBoxChild + */ + +static void +table_child_set_position (ClutterTableChild *self, + gint row, + gint col) +{ + gboolean row_changed = FALSE, col_changed = FALSE; + + if (self->row != row) + { + self->row = row; + + row_changed = TRUE; + } + + if (self->col != col) + { + self->col = col; + + col_changed = TRUE; + } + + if (row_changed || col_changed) + { + ClutterLayoutManager *layout; + ClutterTableLayoutPrivate *priv; + + layout = clutter_layout_meta_get_manager (CLUTTER_LAYOUT_META (self)); + priv = CLUTTER_TABLE_LAYOUT (layout)->priv; + + g_object_freeze_notify (G_OBJECT (self)); + + if (priv->use_animations) + { + clutter_layout_manager_begin_animation (layout, + priv->easing_duration, + priv->easing_mode); + } + else + clutter_layout_manager_layout_changed (layout); + + if (row_changed) + g_object_notify (G_OBJECT (self), "row"); + + if (col_changed) + g_object_notify (G_OBJECT (self), "column"); + + g_object_thaw_notify (G_OBJECT (self)); + } +} + +static void +table_child_set_span (ClutterTableChild *self, + gint row_span, + gint col_span) +{ + gboolean row_changed = FALSE, col_changed = FALSE; + + if (self->row_span != row_span) + { + self->row_span = row_span; + + row_changed = TRUE; + } + + if (self->col_span != col_span) + { + self->col_span = col_span; + + col_changed = TRUE; + } + + if (row_changed || col_changed) + { + ClutterLayoutManager *layout; + ClutterTableLayout *table; + + layout = clutter_layout_meta_get_manager (CLUTTER_LAYOUT_META (self)); + table = CLUTTER_TABLE_LAYOUT (layout); + + if (table->priv->use_animations) + { + clutter_layout_manager_begin_animation (layout, + table->priv->easing_duration, + table->priv->easing_mode); + } + else + clutter_layout_manager_layout_changed (layout); + + if (row_changed) + g_object_notify (G_OBJECT (self), "row-span"); + + if (col_changed) + g_object_notify (G_OBJECT (self), "column-span"); + } +} + +static void +table_child_set_align (ClutterTableChild *self, + ClutterTableAlignment x_align, + ClutterTableAlignment 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; + ClutterTableLayout *table; + + layout = clutter_layout_meta_get_manager (CLUTTER_LAYOUT_META (self)); + table = CLUTTER_TABLE_LAYOUT (layout); + + if (table->priv->use_animations) + { + clutter_layout_manager_begin_animation (layout, + table->priv->easing_duration, + table->priv->easing_mode); + } + else + 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 +table_child_set_fill (ClutterTableChild *self, + gboolean x_fill, + gboolean y_fill) +{ + gboolean x_changed = FALSE, y_changed = FALSE; + + x_fill = !!x_fill; + y_fill = !!y_fill; + + 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; + ClutterTableLayoutPrivate *priv; + + layout = clutter_layout_meta_get_manager (CLUTTER_LAYOUT_META (self)); + priv = CLUTTER_TABLE_LAYOUT (layout)->priv; + + g_object_freeze_notify (G_OBJECT (self)); + + if (priv->use_animations) + { + clutter_layout_manager_begin_animation (layout, + priv->easing_duration, + priv->easing_mode); + } + else + 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"); + + g_object_thaw_notify (G_OBJECT (self)); + } +} + +static void +table_child_set_expand (ClutterTableChild *self, + gboolean x_expand, + gboolean y_expand) +{ + gboolean x_changed = FALSE, y_changed = FALSE; + + x_expand = !!x_expand; + y_expand = !!y_expand; + + if (self->x_expand != x_expand) + { + self->x_expand = x_expand; + + x_changed = TRUE; + } + + if (self->y_expand != y_expand) + { + self->y_expand = y_expand; + + y_changed = TRUE; + } + + if (x_changed || y_changed) + { + ClutterLayoutManager *layout; + ClutterTableLayoutPrivate *priv; + + layout = clutter_layout_meta_get_manager (CLUTTER_LAYOUT_META (self)); + priv = CLUTTER_TABLE_LAYOUT (layout)->priv; + + g_object_freeze_notify (G_OBJECT (self)); + + if (priv->use_animations) + { + clutter_layout_manager_begin_animation (layout, + priv->easing_duration, + priv->easing_mode); + } + else + clutter_layout_manager_layout_changed (layout); + + if (x_changed) + g_object_notify (G_OBJECT (self), "x-expand"); + + if (y_changed) + g_object_notify (G_OBJECT (self), "y-expand"); + + g_object_thaw_notify (G_OBJECT (self)); + } +} + +static void +clutter_table_child_set_property (GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + ClutterTableChild *self = CLUTTER_TABLE_CHILD (gobject); + + switch (prop_id) + { + case PROP_CHILD_COLUMN: + table_child_set_position (self, + self->row, + g_value_get_int (value)); + break; + + case PROP_CHILD_ROW: + table_child_set_position (self, + g_value_get_int (value), + self->col); + break; + + case PROP_CHILD_COLUMN_SPAN: + table_child_set_span (self, + self->row_span, + g_value_get_int (value)); + break; + + case PROP_CHILD_ROW_SPAN: + table_child_set_span (self, + g_value_get_int (value), + self->col_span); + break; + + case PROP_CHILD_X_ALIGN: + table_child_set_align (self, + g_value_get_enum (value), + self->y_align); + break; + + case PROP_CHILD_Y_ALIGN: + table_child_set_align (self, + self->x_align, + g_value_get_enum (value)); + break; + + case PROP_CHILD_X_FILL: + table_child_set_fill (self, + g_value_get_boolean (value), + self->y_fill); + break; + + case PROP_CHILD_Y_FILL: + table_child_set_fill (self, + self->x_fill, + g_value_get_boolean (value)); + break; + + case PROP_CHILD_X_EXPAND: + table_child_set_expand (self, + g_value_get_boolean (value), + self->y_expand); + break; + + case PROP_CHILD_Y_EXPAND: + table_child_set_expand (self, + self->x_expand, + g_value_get_boolean (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +clutter_table_child_get_property (GObject *gobject, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + ClutterTableChild *self = CLUTTER_TABLE_CHILD (gobject); + + switch (prop_id) + { + case PROP_CHILD_ROW: + g_value_set_int (value, self->row); + break; + + case PROP_CHILD_COLUMN: + g_value_set_int (value, self->col); + break; + + case PROP_CHILD_ROW_SPAN: + g_value_set_int (value, self->row_span); + break; + + case PROP_CHILD_COLUMN_SPAN: + g_value_set_int (value, self->col_span); + break; + + 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_X_EXPAND: + g_value_set_boolean (value, self->x_expand); + break; + + case PROP_CHILD_Y_EXPAND: + g_value_set_boolean (value, self->y_expand); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +clutter_table_child_finalize (GObject *gobject) +{ + ClutterTableChild *self = CLUTTER_TABLE_CHILD (gobject); + + clutter_actor_box_free (self->last_allocation); + + G_OBJECT_CLASS (clutter_table_child_parent_class)->finalize (gobject); +} + +static void +clutter_table_child_class_init (ClutterTableChildClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GParamSpec *pspec; + + gobject_class->set_property = clutter_table_child_set_property; + gobject_class->get_property = clutter_table_child_get_property; + gobject_class->finalize = clutter_table_child_finalize; + + pspec = g_param_spec_int ("column", + P_("Column Number"), + P_("The column the widget resides in"), + 0, G_MAXINT, + 0, + CLUTTER_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_CHILD_COLUMN, pspec); + + pspec = g_param_spec_int ("row", + P_("Row Number"), + P_("The row the widget resides in"), + 0, G_MAXINT, + 0, + CLUTTER_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_CHILD_ROW, pspec); + + pspec = g_param_spec_int ("column-span", + P_("Column Span"), + P_("The number of columns the widget should span"), + 1, G_MAXINT, + 1, + CLUTTER_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_CHILD_COLUMN_SPAN, pspec); + + pspec = g_param_spec_int ("row-span", + P_("Row Span"), + P_("The number of rows the widget should span"), + 1, G_MAXINT, + 1, + CLUTTER_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_CHILD_ROW_SPAN, pspec); + + pspec = g_param_spec_boolean ("x-expand", + P_("Horizontal Expand"), + P_("Allocate extra space for the child in horizontal axis"), + FALSE, + CLUTTER_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_CHILD_X_EXPAND, pspec); + + pspec = g_param_spec_boolean ("y-expand", + P_("Vertical Expand"), + P_("Allocate extra space for the child in vertical axis"), + FALSE, + CLUTTER_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_CHILD_Y_EXPAND, pspec); + + pspec = g_param_spec_boolean ("x-fill", + P_("Horizontal Fill"), + P_("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", + P_("Vertical Fill"), + P_("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); + + /** + * ClutterTableLayout:x-align: + * + * The horizontal alignment of the actor within the cell + * + * Since: 1.4 + */ + pspec = g_param_spec_enum ("x-align", + P_("Horizontal Alignment"), + P_("Horizontal alignment of the actor within the cell"), + CLUTTER_TYPE_TABLE_ALIGNMENT, + CLUTTER_TABLE_ALIGNMENT_CENTER, + CLUTTER_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_CHILD_X_ALIGN, pspec); + + /** + * ClutterTableLayout:y-align: + * + * The vertical alignment of the actor within the cell + * + * Since: 1.4 + */ + pspec = g_param_spec_enum ("y-align", + P_("Vertical Alignment"), + P_("Vertical alignment of the actor within the cell"), + CLUTTER_TYPE_TABLE_ALIGNMENT, + CLUTTER_TABLE_ALIGNMENT_CENTER, + CLUTTER_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_CHILD_Y_ALIGN, pspec); +} + +static void +clutter_table_child_init (ClutterTableChild *self) +{ + self->col_span = 1; + self->row_span = 1; + + self->x_align = CLUTTER_TABLE_ALIGNMENT_CENTER; + self->y_align = CLUTTER_TABLE_ALIGNMENT_CENTER; + + self->x_expand = TRUE; + self->y_expand = TRUE; + + self->x_fill = TRUE; + self->y_fill = TRUE; + + self->last_allocation = NULL; +} + +static GType +clutter_table_layout_get_child_meta_type (ClutterLayoutManager *manager) +{ + return CLUTTER_TYPE_TABLE_CHILD; +} + +static void +clutter_table_layout_set_container (ClutterLayoutManager *layout, + ClutterContainer *container) +{ + ClutterTableLayoutPrivate *priv = CLUTTER_TABLE_LAYOUT (layout)->priv; + + priv->container = container; +} + + +static void +update_row_col (ClutterTableLayout *layout, + ClutterContainer *container) +{ + ClutterTableLayoutPrivate *priv = layout->priv; + ClutterLayoutManager *manager = CLUTTER_LAYOUT_MANAGER (layout); + GList *children, *l; + gint n_cols, n_rows; + + n_cols = n_rows = 0; + children = container ? clutter_container_get_children (container) : NULL; + + for (l = children; l; l = g_list_next (l)) + { + ClutterActor *child = l->data; + ClutterTableChild *meta; + + meta = CLUTTER_TABLE_CHILD (clutter_layout_manager_get_child_meta (manager, container, child)); + + n_cols = MAX (n_cols, meta->col + meta->col_span); + n_rows = MAX (n_rows, meta->row + meta->row_span); + } + + g_list_free (children); + + priv->n_cols = n_cols; + priv->n_rows = n_rows; + +} + +static void +calculate_col_widths (ClutterTableLayout *self, + ClutterContainer *container, + gint for_width) +{ + ClutterTableLayoutPrivate *priv = self->priv; + ClutterLayoutManager *manager = CLUTTER_LAYOUT_MANAGER (self); + gint i; + DimensionData *columns; + GList *l, *children; + + update_row_col (self, container); + g_array_set_size (priv->columns, 0); + g_array_set_size (priv->columns, priv->n_cols); + columns = (DimensionData *) priv->columns->data; + + /* reset the visibility of all columns */ + priv->visible_cols = 0; + for (i = 0; i < priv->n_cols; i++) + columns[i].visible = FALSE; + + children = clutter_container_get_children (container); + + /* STAGE ONE: calculate column widths for non-spanned children */ + for (l = children; l; l = g_list_next (l)) + { + ClutterActor *child = l->data; + ClutterTableChild *meta; + DimensionData *col; + gfloat c_min, c_pref; + + if (!CLUTTER_ACTOR_IS_VISIBLE (child)) + continue; + + meta = (ClutterTableChild *) + clutter_layout_manager_get_child_meta (manager, container, child); + + if (meta->col_span > 1) + continue; + + col = &columns[meta->col]; + + if (!col->visible) + { + col->visible = TRUE; + priv->visible_cols += 1; + } + + clutter_actor_get_preferred_width (child, -1, &c_min, &c_pref); + + col->min_size = MAX (col->min_size, c_min); + col->pref_size = MAX (col->pref_size, c_pref); + col->expand = MAX (col->expand, meta->x_expand); + } + + /* STAGE TWO: take spanning children into account */ + for (l = children; l; l = g_list_next (l)) + { + ClutterActor *child = l->data; + ClutterTableChild *meta; + DimensionData *col; + gfloat c_min, c_pref; + gfloat min_width, pref_width; + gint start_col, end_col; + gint n_expand; + + if (!CLUTTER_ACTOR_IS_VISIBLE (child)) + continue; + + meta = (ClutterTableChild *) + clutter_layout_manager_get_child_meta (manager, container, child); + + if (meta->col_span < 2) + continue; + + col = &columns[meta->col]; + start_col = meta->col; + end_col = meta->col + meta->col_span - 1; + + clutter_actor_get_preferred_width (child, -1, &c_min, &c_pref); + + /* check there is enough room for this actor */ + min_width = 0; + pref_width = 0; + n_expand = 0; + for (i = start_col; i <= end_col; i++) + { + min_width += columns[i].min_size; + pref_width += columns[i].pref_size; + + if (columns[i].expand) + n_expand++; + + if (!col->visible) + { + col->visible = TRUE; + priv->visible_cols += 1; + } + } + min_width += priv->col_spacing * (meta->col_span - 1); + pref_width += priv->col_spacing * (meta->col_span - 1); + + /* see calculate_row_heights() for comments */ + /* (1) */ + if (c_min > min_width) + { + + /* (2) */ + /* we can start from preferred width and decrease */ + if (pref_width > c_min) + { + for (i = start_col; i <= end_col; i++) + columns[i].final_size = columns[i].pref_size; + + while (pref_width > c_min) + { + for (i = start_col; i <= end_col; i++) + { + if (columns[i].final_size > columns[i].min_size) + { + columns[i].final_size--; + pref_width--; + } + } + } + + for (i = start_col; i <= end_col; i++) + columns[i].min_size = columns[i].final_size; + } + else + { + /* (3) */ + /* we can expand from preferred size */ + gfloat expand_by; + + expand_by = c_pref - pref_width; + + for (i = start_col; i <= end_col; i++) + { + if (n_expand) + { + if (columns[i].expand) + columns[i].min_size = columns[i].pref_size + + expand_by / n_expand; + } + else + columns[i].min_size = columns[i].pref_size + + expand_by / meta->col_span; + } + } + } + + + } + g_list_free (children); + + /* calculate final widths */ + if (for_width >= 0) + { + gfloat min_width, pref_width; + gint n_expand; + + min_width = 0; + pref_width = 0; + n_expand = 0; + for (i = 0; i < self->priv->n_cols; i++) + { + pref_width += columns[i].pref_size; + min_width += columns[i].min_size; + if (columns[i].expand) + n_expand++; + } + pref_width += priv->col_spacing * (priv->n_cols - 1); + min_width += priv->col_spacing * (priv->n_cols - 1); + + if (for_width <= min_width) + { + /* erk, we can't shrink this! */ + for (i = 0; i < priv->n_cols; i++) + columns[i].final_size = columns[i].min_size; + + return; + } + + if (for_width == pref_width) + { + /* perfect! */ + for (i = 0; i < self->priv->n_cols; i++) + columns[i].final_size = columns[i].pref_size; + + return; + } + + /* for_width is between min_width and pref_width */ + if (for_width < pref_width && for_width > min_width) + { + gfloat width; + + /* shrink columns until they reach min_width */ + + /* start with all columns at preferred size */ + for (i = 0; i < self->priv->n_cols; i++) + columns[i].final_size = columns[i].pref_size; + + width = pref_width; + + while (width > for_width) + { + for (i = 0; i < self->priv->n_cols; i++) + { + if (columns[i].final_size > columns[i].min_size) + { + columns[i].final_size--; + width--; + } + } + } + + return; + } + + /* expand columns */ + if (for_width > pref_width) + { + gfloat extra_width = for_width - pref_width; + gint remaining; + + if (n_expand) + remaining = (gint) extra_width % n_expand; + else + remaining = (gint) extra_width % priv->n_cols; + + for (i = 0; i < self->priv->n_cols; i++) + { + if (columns[i].expand) + { + if (n_expand) + columns[i].final_size = columns[i].pref_size + + (extra_width / n_expand); + else + columns[i].final_size = columns[i].pref_size + + (extra_width / priv->n_cols); + } + else + columns[i].final_size = columns[i].pref_size; + } + + /* distribute the remainder among children */ + i = 0; + while (remaining) + { + columns[i].final_size++; + i++; + remaining--; + } + } + } + +} + +static void +calculate_row_heights (ClutterTableLayout *self, + ClutterContainer *container, + gint for_height) +{ + ClutterTableLayoutPrivate *priv = self->priv; + ClutterLayoutManager *manager = CLUTTER_LAYOUT_MANAGER (self); + GList *l, *children; + gint i; + DimensionData *rows, *columns; + + update_row_col (self, container); + g_array_set_size (priv->rows, 0); + g_array_set_size (priv->rows, self->priv->n_rows); + + rows = (DimensionData*) priv->rows->data; + columns = (DimensionData*) priv->columns->data; + + /* reset the visibility of all rows */ + priv->visible_rows = 0; + for (i = 0; i < priv->n_rows; i++) + rows[i].visible = FALSE; + + children = clutter_container_get_children (container); + /* STAGE ONE: calculate row heights for non-spanned children */ + for (l = children; l; l = g_list_next (l)) + { + ClutterActor *child = l->data; + ClutterTableChild *meta; + DimensionData *row; + gfloat c_min, c_pref; + + if (!CLUTTER_ACTOR_IS_VISIBLE (child)) + continue; + + meta = (ClutterTableChild *) + clutter_layout_manager_get_child_meta (manager, container, child); + + if (meta->row_span > 1) + continue; + + row = &rows[meta->row]; + + if (!row->visible) + { + row->visible = TRUE; + priv->visible_rows += 1; + } + + clutter_actor_get_preferred_height (child, columns[meta->col].final_size, + &c_min, &c_pref); + + row->min_size = MAX (row->min_size, c_min); + row->pref_size = MAX (row->pref_size, c_pref); + row->expand = MAX (row->expand, meta->y_expand); + } + + + + /* STAGE TWO: take spanning children into account */ + for (l = children; l; l = g_list_next (l)) + { + ClutterActor *child = l->data; + ClutterTableChild *meta; + DimensionData *row; + gfloat c_min, c_pref; + gfloat min_height, pref_height; + gint start_row, end_row; + gint n_expand; + + if (!CLUTTER_ACTOR_IS_VISIBLE (child)) + continue; + + meta = (ClutterTableChild *) + clutter_layout_manager_get_child_meta (manager, container, child); + + if (meta->row_span < 2) + continue; + + row = &rows[meta->row]; + start_row = meta->row; + end_row = meta->row + meta->row_span - 1; + + clutter_actor_get_preferred_height (child, columns[meta->col].final_size, + &c_min, &c_pref); + + + /* check there is enough room for this actor */ + min_height = 0; + pref_height = 0; + n_expand = 0; + for (i = start_row; i <= end_row; i++) + { + min_height += rows[i].min_size; + pref_height += rows[i].pref_size; + + if (rows[i].expand) + n_expand++; + + if (!rows[i].visible) + { + rows[i].visible = TRUE; + priv->visible_rows += 1; + } + } + min_height += priv->row_spacing * (meta->row_span - 1); + pref_height += priv->row_spacing * (meta->row_span - 1); + + /* 1) If the minimum height of the rows spanned is less than the + * minimum height of the child that is spanning them, then we + * must increase the minimum height of the rows spanned. + * + * 2) If the preferred height of the spanned rows is more than + * the minimum height of the spanning child, then we can start + * at this size and decrease each row evenly. + * + * 3) If the preferred height of the rows is more than the minimum + * height of the spanned child, then we can start at the preferred + * height and expand. + */ + + /* (1) */ + if (c_min > min_height) + { + + /* (2) */ + /* we can start from preferred height and decrease */ + if (pref_height > c_min) + { + for (i = start_row; i <= end_row; i++) + rows[i].final_size = rows[i].pref_size; + + while (pref_height > c_min) + { + for (i = start_row; i <= end_row; i++) + { + if (rows[i].final_size > rows[i].min_size) + { + rows[i].final_size--; + pref_height--; + } + } + } + + for (i = start_row; i <= end_row; i++) + rows[i].min_size = rows[i].final_size; + } + else + { + /* (3) */ + /* we can expand from preferred size */ + gfloat expand_by = c_pref - pref_height; + + for (i = start_row; i <= end_row; i++) + { + if (n_expand) + { + if (rows[i].expand) + rows[i].min_size = rows[i].pref_size + + expand_by / n_expand; + } + else + rows[i].min_size = rows[i].pref_size + + expand_by / meta->row_span; + } + } + } + + } + + g_list_free (children); + + /* calculate final heights */ + if (for_height >= 0) + { + gfloat min_height, pref_height; + gint n_expand; + + min_height = 0; + pref_height = 0; + n_expand = 0; + for (i = 0; i < self->priv->n_rows; i++) + { + pref_height += rows[i].pref_size; + min_height += rows[i].min_size; + if (rows[i].expand) + n_expand++; + } + pref_height += priv->row_spacing * (priv->n_rows - 1); + min_height += priv->row_spacing * (priv->n_rows - 1); + + if (for_height <= min_height) + { + /* erk, we can't shrink this! */ + for (i = 0; i < self->priv->n_rows; i++) + rows[i].final_size = rows[i].min_size; + + return; + } + + if (for_height == pref_height) + { + /* perfect! */ + for (i = 0; i < self->priv->n_rows; i++) + rows[i].final_size = rows[i].pref_size; + + return; + } + + /* for_height is between min_height and pref_height */ + if (for_height < pref_height && for_height > min_height) + { + gfloat height; + + /* shrink rows until they reach min_height */ + + /* start with all rows at preferred size */ + for (i = 0; i < self->priv->n_rows; i++) + rows[i].final_size = rows[i].pref_size; + + height = pref_height; + + while (height > for_height) + { + for (i = 0; i < priv->n_rows; i++) + { + if (rows[i].final_size > rows[i].min_size) + { + rows[i].final_size--; + height--; + } + } + } + + return; + } + + /* expand rows */ + if (for_height > pref_height) + { + gfloat extra_height = for_height - pref_height; + gint remaining; + + if (n_expand) + remaining = (gint) extra_height % n_expand; + else + remaining = (gint) extra_height % self->priv->n_rows; + + for (i = 0; i < self->priv->n_rows; i++) + { + if (rows[i].expand) + { + if (n_expand) + rows[i].final_size = rows[i].pref_size + + (extra_height / n_expand); + else + rows[i].final_size = rows[i].pref_size + + (extra_height / priv->n_rows); + } + else + rows[i].final_size = rows[i].pref_size; + } + + /* distribute the remainder among children */ + i = 0; + while (remaining) + { + rows[i].final_size++; + i++; + remaining--; + } + } + } + +} + +static void +calculate_table_dimensions (ClutterTableLayout *self, + ClutterContainer *container, + gfloat for_width, + gfloat for_height) +{ + calculate_col_widths (self, container, for_width); + calculate_row_heights (self, container, for_height); +} + +static void +clutter_table_layout_get_preferred_width (ClutterLayoutManager *layout, + ClutterContainer *container, + gfloat for_height, + gfloat *min_width_p, + gfloat *natural_width_p) +{ + ClutterTableLayout *self = CLUTTER_TABLE_LAYOUT (layout); + ClutterTableLayoutPrivate *priv = self->priv; + gfloat total_min_width, total_pref_width; + DimensionData *columns; + gint i; + + update_row_col (self, container); + if (priv->n_cols < 1) + { + *min_width_p = 0; + *natural_width_p = 0; + return; + } + + calculate_table_dimensions (self, container, -1, for_height); + columns = (DimensionData *) priv->columns->data; + + total_min_width = (priv->visible_cols - 1) * (float) priv->col_spacing; + total_pref_width = total_min_width; + + for (i = 0; i < priv->n_cols; i++) + { + total_min_width += columns[i].min_size; + total_pref_width += columns[i].pref_size; + } + + if (min_width_p) + *min_width_p = total_min_width; + + if (natural_width_p) + *natural_width_p = total_pref_width; +} + +static void +clutter_table_layout_get_preferred_height (ClutterLayoutManager *layout, + ClutterContainer *container, + gfloat for_width, + gfloat *min_height_p, + gfloat *natural_height_p) +{ + ClutterTableLayout *self = CLUTTER_TABLE_LAYOUT (layout); + ClutterTableLayoutPrivate *priv = self->priv; + gfloat total_min_height, total_pref_height; + DimensionData *rows; + gint i; + + update_row_col (self, container); + if (priv->n_rows < 1) + { + *min_height_p = 0; + *natural_height_p = 0; + return; + } + + calculate_table_dimensions (self, container, for_width, -1); + rows = (DimensionData *) priv->rows->data; + + total_min_height = (priv->visible_rows - 1) * (float) priv->row_spacing; + total_pref_height = total_min_height; + + for (i = 0; i < self->priv->n_rows; i++) + { + total_min_height += rows[i].min_size; + total_pref_height += rows[i].pref_size; + } + + if (min_height_p) + *min_height_p = total_min_height; + + if (natural_height_p) + *natural_height_p = total_pref_height; +} + +static gdouble +get_table_alignment_factor (ClutterTableAlignment alignment) +{ + switch (alignment) + { + case CLUTTER_TABLE_ALIGNMENT_START: + return 0.0; + + case CLUTTER_TABLE_ALIGNMENT_CENTER: + return 0.5; + + case CLUTTER_TABLE_ALIGNMENT_END: + return 1.0; + } + + return 0.0; +} + +static void +clutter_table_layout_allocate (ClutterLayoutManager *layout, + ClutterContainer *container, + const ClutterActorBox *box, + ClutterAllocationFlags flags) +{ + ClutterTableLayout *self = CLUTTER_TABLE_LAYOUT (layout); + ClutterTableLayoutPrivate *priv = self->priv; + GList *list, *children; + gint row_spacing, col_spacing; + gint i, table_width, table_height; + DimensionData *rows, *columns; + + update_row_col (self, container); + if (priv->n_cols < 1 || priv->n_rows < 1) + return; + + children = clutter_container_get_children (container); + if (children == NULL) + return; + + col_spacing = (priv->col_spacing); + row_spacing = (priv->row_spacing); + + + table_height = (int)(box->y2 - box->y1); + table_width = (int)(box->x2 - box->x1); + + calculate_table_dimensions (self, container, box->x2 - box->x1, box->y2 - box->y1); + + rows = (DimensionData *) priv->rows->data; + columns = (DimensionData *) priv->columns->data; + + for (list = children; list; list = g_list_next (list)) + { + ClutterActor *child = list->data; + gint row, col, row_span, col_span; + gint col_width, row_height; + ClutterTableChild *meta; + ClutterActorBox childbox; + gint child_x, child_y; + gdouble x_align, y_align; + gboolean x_fill, y_fill; + + if (!CLUTTER_ACTOR_IS_VISIBLE (child)) + continue; + + meta = (ClutterTableChild *) + clutter_layout_manager_get_child_meta (layout, priv->container, child); + + /* get child properties */ + col = meta->col; + row = meta->row; + row_span = meta->row_span; + col_span = meta->col_span; + x_align = get_table_alignment_factor (meta->x_align); + y_align = get_table_alignment_factor (meta->y_align); + x_fill = meta->x_fill; + y_fill = meta->y_fill; + + /* initialise the width and height */ + col_width = columns[col].final_size; + row_height = rows[row].final_size; + + /* Add the widths of the spanned columns: + * + * First check that we have a non-zero span. Then we loop over each of + * the columns that we're spanning but we stop short if we go past the + * number of columns in the table. This is necessary to avoid accessing + * uninitialised memory. We add the spacing in here too since we only + * want to add as much spacing as times we successfully span. + */ + if (col + col_span > priv->n_cols) + g_warning (G_STRLOC ": column-span exceeds number of columns"); + if (row + row_span > priv->n_rows) + g_warning (G_STRLOC ": row-span exceeds number of rows"); + + if (col_span > 1) + { + for (i = col + 1; i < col + col_span && i < priv->n_cols; i++) + { + col_width += columns[i].final_size; + col_width += col_spacing; + } + } + + /* add the height of the spanned rows */ + if (row_span > 1) + { + for (i = row + 1; i < row + row_span && i < priv->n_rows; i++) + { + row_height += rows[i].final_size; + row_height += row_spacing; + } + } + + /* calculate child x */ + child_x = 0.0f; + for (i = 0; i < col; i++) + { + if (columns[i].visible) + { + child_x += columns[i].final_size; + child_x += col_spacing; + } + } + + /* calculate child y */ + child_y = 0.0f; + for (i = 0; i < row; i++) + { + if (rows[i].visible) + { + child_y += rows[i].final_size; + child_y += row_spacing; + } + } + + /* set up childbox */ + childbox.x1 = (float) child_x; + childbox.x2 = (float) MAX (0, child_x + col_width); + + childbox.y1 = (float) child_y; + childbox.y2 = (float) MAX (0, child_y + row_height); + + clutter_actor_allocate_align_fill (child, &childbox, + x_align, y_align, + x_fill, y_fill, + flags); + + /* since we call this after allocate_align_fill(), this is + * just a cheap copy + */ + clutter_actor_get_allocation_box (child, &childbox); + + if (priv->use_animations && priv->is_animating) + { + ClutterActorBox *start = NULL; + ClutterActorBox end = { 0, }; + gdouble p; + + p = clutter_layout_manager_get_animation_progress (layout); + + start = meta->last_allocation; + if (start == NULL) + { + /* if there is no allocation available then the child has just + * been added to the container; we put it in the final state + * and store its allocation for later + */ + meta->last_allocation = clutter_actor_box_copy (&childbox); + + goto do_allocate; + } + + end = childbox; + + /* interpolate between the initial and final values */ + clutter_actor_box_interpolate (start, &end, p, &childbox); + + CLUTTER_NOTE (ANIMATION, + "Animate { %.1f, %.1f, %.1f, %.1f }\t" + "%.3f * { %.1f, %.1f, %.1f, %.1f }\t" + "-> { %.1f, %.1f, %.1f, %.1f }", + start->x1, start->y1, + start->x2, start->y2, + p, + childbox.x1, childbox.y1, + childbox.x2, childbox.y2, + end.x1, end.y1, + end.x2, end.y2); + } + else + { + /* store the allocation for later animations */ + meta->last_allocation = clutter_actor_box_copy (&childbox); + } + + do_allocate: + clutter_actor_allocate (child, &childbox, flags); + } + + g_list_free (children); +} + +static ClutterAlpha * +clutter_table_layout_begin_animation (ClutterLayoutManager *manager, + guint duration, + gulong easing) +{ + ClutterTableLayoutPrivate *priv = CLUTTER_TABLE_LAYOUT (manager)->priv; + ClutterLayoutManagerClass *parent_class; + + priv->is_animating = TRUE; + + /* we want the default implementation */ + parent_class = CLUTTER_LAYOUT_MANAGER_CLASS (clutter_table_layout_parent_class); + + return parent_class->begin_animation (manager, duration, easing); +} + +static void +clutter_table_layout_end_animation (ClutterLayoutManager *manager) +{ + ClutterTableLayoutPrivate *priv = CLUTTER_TABLE_LAYOUT (manager)->priv; + ClutterLayoutManagerClass *parent_class; + + priv->is_animating = FALSE; + + /* we want the default implementation */ + parent_class = CLUTTER_LAYOUT_MANAGER_CLASS (clutter_table_layout_parent_class); + parent_class->end_animation (manager); +} + +static void +clutter_table_layout_set_property (GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + ClutterTableLayout *self = CLUTTER_TABLE_LAYOUT (gobject); + + switch (prop_id) + { + case PROP_COLUMN_SPACING: + clutter_table_layout_set_column_spacing (self, g_value_get_uint (value)); + break; + + case PROP_ROW_SPACING: + clutter_table_layout_set_row_spacing (self, g_value_get_uint (value)); + break; + + case PROP_USE_ANIMATIONS: + clutter_table_layout_set_use_animations (self, g_value_get_boolean (value)); + break; + + case PROP_EASING_MODE: + clutter_table_layout_set_easing_mode (self, g_value_get_ulong (value)); + break; + + case PROP_EASING_DURATION: + clutter_table_layout_set_easing_duration (self, g_value_get_uint (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +clutter_table_layout_get_property (GObject *gobject, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + ClutterTableLayoutPrivate *priv = CLUTTER_TABLE_LAYOUT (gobject)->priv; + + switch (prop_id) + { + case PROP_ROW_SPACING: + g_value_set_uint (value, priv->row_spacing); + break; + + case PROP_COLUMN_SPACING: + g_value_set_uint (value, priv->col_spacing); + break; + + case PROP_USE_ANIMATIONS: + g_value_set_boolean (value, priv->use_animations); + break; + + case PROP_EASING_MODE: + g_value_set_ulong (value, priv->easing_mode); + break; + + case PROP_EASING_DURATION: + g_value_set_uint (value, priv->easing_duration); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +clutter_table_layout_finalize (GObject *gobject) +{ + ClutterTableLayoutPrivate *priv = CLUTTER_TABLE_LAYOUT (gobject)->priv; + + g_array_free (priv->columns, TRUE); + g_array_free (priv->rows, TRUE); + + G_OBJECT_CLASS (clutter_table_layout_parent_class)->finalize (gobject); +} + +static void +clutter_table_layout_class_init (ClutterTableLayoutClass *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_table_layout_set_property; + gobject_class->get_property = clutter_table_layout_get_property; + gobject_class->finalize = clutter_table_layout_finalize; + + layout_class->get_preferred_width = + clutter_table_layout_get_preferred_width; + layout_class->get_preferred_height = + clutter_table_layout_get_preferred_height; + layout_class->allocate = clutter_table_layout_allocate; + layout_class->set_container = clutter_table_layout_set_container; + layout_class->get_child_meta_type = + clutter_table_layout_get_child_meta_type; + layout_class->begin_animation = clutter_table_layout_begin_animation; + layout_class->end_animation = clutter_table_layout_end_animation; + + g_type_class_add_private (klass, sizeof (ClutterTableLayoutPrivate)); + + /** + * ClutterTableLayout:column-spacing: + * + * The spacing between columns of the #ClutterTableLayout, in pixels + * + * Since: 1.4 + */ + pspec = g_param_spec_uint ("column-spacing", + "Column Spacing", + "Spacing between columns", + 0, G_MAXUINT, 0, + CLUTTER_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_COLUMN_SPACING, pspec); + + /** + * ClutterTableLayout:row-spacing: + * + * The spacing between rows of the #ClutterTableLayout, in pixels + * + * Since: 1.4 + */ + pspec = g_param_spec_uint ("row-spacing", + "Row Spacing", + "Spacing between rows", + 0, G_MAXUINT, 0, + CLUTTER_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_ROW_SPACING, pspec); + + /** + * ClutterTableLayout:use-animations: + * + * Whether the #ClutterTableLayout should animate changes in the + * layout properties + * + * Since: 1.4 + */ + pspec = g_param_spec_boolean ("use-animations", + "Use Animations", + "Whether layout changes should be animated", + FALSE, + CLUTTER_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_USE_ANIMATIONS, pspec); + + /** + * ClutterTableLayout:easing-mode: + * + * The easing mode for the animations, in case + * #ClutterTableLayout:use-animations is set to %TRUE + * + * The easing mode has the same semantics of #ClutterAnimation:mode: it can + * either be a value from the #ClutterAnimationMode enumeration, like + * %CLUTTER_EASE_OUT_CUBIC, or a logical id as returned by + * clutter_alpha_register_func() + * + * The default value is %CLUTTER_EASE_OUT_CUBIC + * + * Since: 1.4 + */ + pspec = g_param_spec_ulong ("easing-mode", + "Easing Mode", + "The easing mode of the animations", + 0, G_MAXULONG, + CLUTTER_EASE_OUT_CUBIC, + CLUTTER_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_EASING_MODE, pspec); + + /** + * ClutterTableLayout:easing-duration: + * + * The duration of the animations, in case #ClutterTableLayout:use-animations + * is set to %TRUE + * + * The duration is expressed in milliseconds + * + * Since: 1.4 + */ + pspec = g_param_spec_uint ("easing-duration", + "Easing Duration", + "The duration of the animations", + 0, G_MAXUINT, + 500, + CLUTTER_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_EASING_DURATION, pspec); +} + +static void +clutter_table_layout_init (ClutterTableLayout *layout) +{ + ClutterTableLayoutPrivate *priv; + + layout->priv = priv = CLUTTER_TABLE_LAYOUT_GET_PRIVATE (layout); + + priv->row_spacing = 0; + priv->col_spacing = 0; + + priv->use_animations = FALSE; + priv->easing_mode = CLUTTER_EASE_OUT_CUBIC; + priv->easing_duration = 500; + + priv->columns = g_array_new (FALSE, TRUE, sizeof (DimensionData)); + priv->rows = g_array_new (FALSE, TRUE, sizeof (DimensionData)); +} + +/** + * clutter_table_layout_new: + * + * Creates a new #ClutterTableLayout layout manager + * + * Return value: the newly created #ClutterTableLayout + * + * Since: 1.4 + */ +ClutterLayoutManager * +clutter_table_layout_new (void) +{ + return g_object_new (CLUTTER_TYPE_TABLE_LAYOUT, NULL); +} + +/** + * clutter_table_layout_set_column_spacing: + * @layout: a #ClutterTableLayout + * @spacing: the spacing between columns of the layout, in pixels + * + * Sets the spacing between columns of @layout + * + * Since: 1.4 + */ +void +clutter_table_layout_set_column_spacing (ClutterTableLayout *layout, + guint spacing) +{ + ClutterTableLayoutPrivate *priv; + + g_return_if_fail (CLUTTER_IS_TABLE_LAYOUT (layout)); + + priv = layout->priv; + + if (priv->col_spacing != spacing) + { + ClutterLayoutManager *manager; + + priv->col_spacing = spacing; + + manager = CLUTTER_LAYOUT_MANAGER (layout); + + if (priv->use_animations) + { + clutter_layout_manager_begin_animation (manager, + priv->easing_duration, + priv->easing_mode); + } + else + clutter_layout_manager_layout_changed (manager); + + g_object_notify (G_OBJECT (layout), "column-spacing"); + } +} + +/** + * clutter_table_layout_get_column_spacing: + * @layout: a #ClutterTableLayout + * + * Retrieves the spacing set using clutter_table_layout_set_column_spacing() + * + * Return value: the spacing between columns of the #ClutterTableLayout + * + * Since: 1.4 + */ +guint +clutter_table_layout_get_column_spacing (ClutterTableLayout *layout) +{ + g_return_val_if_fail (CLUTTER_IS_TABLE_LAYOUT (layout), 0); + + return layout->priv->col_spacing; +} + +/** + * clutter_table_layout_set_row_spacing: + * @layout: a #ClutterTableLayout + * @spacing: the spacing between rows of the layout, in pixels + * + * Sets the spacing between rows of @layout + * + * Since: 1.4 + */ +void +clutter_table_layout_set_row_spacing (ClutterTableLayout *layout, + guint spacing) +{ + ClutterTableLayoutPrivate *priv; + + g_return_if_fail (CLUTTER_IS_TABLE_LAYOUT (layout)); + + priv = layout->priv; + + if (priv->row_spacing != spacing) + { + ClutterLayoutManager *manager; + + priv->row_spacing = spacing; + + manager = CLUTTER_LAYOUT_MANAGER (layout); + + if (priv->use_animations) + { + clutter_layout_manager_begin_animation (manager, + priv->easing_duration, + priv->easing_mode); + } + else + clutter_layout_manager_layout_changed (manager); + + g_object_notify (G_OBJECT (layout), "row-spacing"); + } +} + +/** + * clutter_table_layout_get_row_spacing: + * @layout: a #ClutterTableLayout + * + * Retrieves the spacing set using clutter_table_layout_set_row_spacing() + * + * Return value: the spacing between rows of the #ClutterTableLayout + * + * Since: 1.4 + */ +guint +clutter_table_layout_get_row_spacing (ClutterTableLayout *layout) +{ + g_return_val_if_fail (CLUTTER_IS_TABLE_LAYOUT (layout), 0); + + return layout->priv->row_spacing; +} + +/** + * clutter_table_layout_pack: + * @layout: a #ClutterTableLayout + * @actor: a #ClutterActor + * @row: the row the @actor should be put, or -1 to append + * @column: the column the @actor should be put, or -1 to append + * + * Packs @actor inside the #ClutterContainer associated to @layout + * at the given row and column. + * + * Since: 1.4 + */ +void +clutter_table_layout_pack (ClutterTableLayout *layout, + ClutterActor *actor, + gint row, + gint column) +{ + ClutterTableLayoutPrivate *priv; + ClutterLayoutManager *manager; + ClutterLayoutMeta *meta; + + g_return_if_fail (CLUTTER_IS_TABLE_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; + } + + update_row_col (CLUTTER_TABLE_LAYOUT (layout), priv->container); + + 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_TABLE_CHILD (meta)); + + if (row < 0) + row = priv->n_rows + 1; + + if (column < 0) + column = priv->n_cols + 1; + + table_child_set_position (CLUTTER_TABLE_CHILD (meta), row, column); +} + +/** + * clutter_table_layout_set_span: + * @layout: a #ClutterTableLayout + * @actor: a #ClutterActor child of @layout + * @row_span: Row span for @actor + * @column_span: Column span for @actor + * + * Sets the row and column span for @actor + * inside @layout + * + * Since: 1.4 + */ +void +clutter_table_layout_set_span (ClutterTableLayout *layout, + ClutterActor *actor, + gint row_span, + gint column_span) +{ + ClutterTableLayoutPrivate *priv; + ClutterLayoutManager *manager; + ClutterLayoutMeta *meta; + + g_return_if_fail (CLUTTER_IS_TABLE_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_TABLE_CHILD (meta)); + + table_child_set_span (CLUTTER_TABLE_CHILD (meta), row_span, column_span); +} + +/** + * clutter_table_layout_get_span: + * @layout: a #ClutterTableLayout + * @actor: a #ClutterActor child of @layout + * @row_span: (out): return location for the row span + * @column_span: (out): return location for the col span + * + * Retrieves the row and column span for @actor as set using + * clutter_table_layout_pack() or clutter_table_layout_set_span() + * + * Since: 1.4 + */ +void +clutter_table_layout_get_span (ClutterTableLayout *layout, + ClutterActor *actor, + gint *row_span, + gint *column_span) +{ + ClutterTableLayoutPrivate *priv; + ClutterLayoutManager *manager; + ClutterLayoutMeta *meta; + + g_return_if_fail (CLUTTER_IS_TABLE_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_TABLE_CHILD (meta)); + + if (row_span) + *row_span = CLUTTER_TABLE_CHILD (meta)->row_span; + + if (column_span) + *column_span = CLUTTER_TABLE_CHILD (meta)->col_span; +} + +/** + * clutter_table_layout_set_alignment: + * @layout: a #ClutterTableLayout + * @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.4 + */ +void +clutter_table_layout_set_alignment (ClutterTableLayout *layout, + ClutterActor *actor, + ClutterTableAlignment x_align, + ClutterTableAlignment y_align) +{ + ClutterTableLayoutPrivate *priv; + ClutterLayoutManager *manager; + ClutterLayoutMeta *meta; + + g_return_if_fail (CLUTTER_IS_TABLE_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_TABLE_CHILD (meta)); + + table_child_set_align (CLUTTER_TABLE_CHILD (meta), x_align, y_align); +} + +/** + * clutter_table_layout_get_alignment: + * @layout: a #ClutterTableLayout + * @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_table_layout_pack() or + * clutter_table_layout_set_alignment(). + * + * Since: 1.4 + */ +void +clutter_table_layout_get_alignment (ClutterTableLayout *layout, + ClutterActor *actor, + ClutterTableAlignment *x_align, + ClutterTableAlignment *y_align) +{ + ClutterTableLayoutPrivate *priv; + ClutterLayoutManager *manager; + ClutterLayoutMeta *meta; + + g_return_if_fail (CLUTTER_IS_TABLE_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_TABLE_CHILD (meta)); + + if (x_align) + *x_align = CLUTTER_TABLE_CHILD (meta)->x_align; + + if (y_align) + *y_align = CLUTTER_TABLE_CHILD (meta)->y_align; +} + +/** + * clutter_table_layout_set_fill: + * @layout: a #ClutterTableLayout + * @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.4 + */ +void +clutter_table_layout_set_fill (ClutterTableLayout *layout, + ClutterActor *actor, + gboolean x_fill, + gboolean y_fill) +{ + ClutterTableLayoutPrivate *priv; + ClutterLayoutManager *manager; + ClutterLayoutMeta *meta; + + g_return_if_fail (CLUTTER_IS_TABLE_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_TABLE_CHILD (meta)); + + table_child_set_fill (CLUTTER_TABLE_CHILD (meta), x_fill, y_fill); +} + +/** + * clutter_table_layout_get_fill: + * @layout: a #ClutterTableLayout + * @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_table_layout_pack() or clutter_table_layout_set_fill() + * + * Since: 1.4 + */ +void +clutter_table_layout_get_fill (ClutterTableLayout *layout, + ClutterActor *actor, + gboolean *x_fill, + gboolean *y_fill) +{ + ClutterTableLayoutPrivate *priv; + ClutterLayoutManager *manager; + ClutterLayoutMeta *meta; + + g_return_if_fail (CLUTTER_IS_TABLE_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_TABLE_CHILD (meta)); + + if (x_fill) + *x_fill = CLUTTER_TABLE_CHILD (meta)->x_fill; + + if (y_fill) + *y_fill = CLUTTER_TABLE_CHILD (meta)->y_fill; +} + + +/** + * clutter_table_layout_set_expand: + * @layout: a #ClutterTableLayout + * @actor: a #ClutterActor child of @layout + * @x_expand: whether @actor should allocate extra space horizontally + * @y_expand: whether @actor should allocate extra space vertically + * + * Sets the horizontal and vertical expand policies for @actor + * inside @layout + * + * Since: 1.4 + */ +void +clutter_table_layout_set_expand (ClutterTableLayout *layout, + ClutterActor *actor, + gboolean x_expand, + gboolean y_expand) +{ + ClutterTableLayoutPrivate *priv; + ClutterLayoutManager *manager; + ClutterLayoutMeta *meta; + + g_return_if_fail (CLUTTER_IS_TABLE_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_TABLE_CHILD (meta)); + + table_child_set_expand (CLUTTER_TABLE_CHILD (meta), x_expand, y_expand); +} + +/** + * clutter_table_layout_get_expand: + * @layout: a #ClutterTableLayout + * @actor: a #ClutterActor child of @layout + * @x_expand: (out): return location for the horizontal expand policy + * @y_expand: (out): return location for the vertical expand policy + * + * Retrieves the horizontal and vertical expand policies for @actor + * as set using clutter_table_layout_pack() or clutter_table_layout_set_expand() + * + * Since: 1.4 + */ +void +clutter_table_layout_get_expand (ClutterTableLayout *layout, + ClutterActor *actor, + gboolean *x_expand, + gboolean *y_expand) +{ + ClutterTableLayoutPrivate *priv; + ClutterLayoutManager *manager; + ClutterLayoutMeta *meta; + + g_return_if_fail (CLUTTER_IS_TABLE_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_TABLE_CHILD (meta)); + + if (x_expand) + *x_expand = CLUTTER_TABLE_CHILD (meta)->x_expand; + + if (y_expand) + *y_expand = CLUTTER_TABLE_CHILD (meta)->y_expand; +} + +/** + * clutter_table_layout_set_use_animations: + * @layout: a #ClutterTableLayout + * @animate: %TRUE if the @layout should use animations + * + * Sets whether @layout should animate changes in the layout properties + * + * The duration of the animations is controlled by + * clutter_table_layout_set_easing_duration(); the easing mode to be used + * by the animations is controlled by clutter_table_layout_set_easing_mode() + * + * Since: 1.4 + */ +void +clutter_table_layout_set_use_animations (ClutterTableLayout *layout, + gboolean animate) +{ + ClutterTableLayoutPrivate *priv; + + g_return_if_fail (CLUTTER_IS_TABLE_LAYOUT (layout)); + + priv = layout->priv; + + animate = !!animate; + if (priv->use_animations != animate) + { + priv->use_animations = animate; + + g_object_notify (G_OBJECT (layout), "use-animations"); + } +} + +/** + * clutter_table_layout_get_use_animations: + * @layout: a #ClutterTableLayout + * + * Retrieves whether @layout should animate changes in the layout properties + * + * Since clutter_table_layout_set_use_animations() + * + * Return value: %TRUE if the animations should be used, %FALSE otherwise + * + * Since: 1.4 + */ +gboolean +clutter_table_layout_get_use_animations (ClutterTableLayout *layout) +{ + g_return_val_if_fail (CLUTTER_IS_TABLE_LAYOUT (layout), FALSE); + + return layout->priv->use_animations; +} + +/** + * clutter_table_layout_set_easing_mode: + * @layout: a #ClutterTableLayout + * @mode: an easing mode, either from #ClutterAnimationMode or a logical id + * from clutter_alpha_register_func() + * + * Sets the easing mode to be used by @layout when animating changes in layout + * properties + * + * Use clutter_table_layout_set_use_animations() to enable and disable the + * animations + * + * Since: 1.4 + */ +void +clutter_table_layout_set_easing_mode (ClutterTableLayout *layout, + gulong mode) +{ + ClutterTableLayoutPrivate *priv; + + g_return_if_fail (CLUTTER_IS_TABLE_LAYOUT (layout)); + + priv = layout->priv; + + if (priv->easing_mode != mode) + { + priv->easing_mode = mode; + + g_object_notify (G_OBJECT (layout), "easing-mode"); + } +} + +/** + * clutter_table_layout_get_easing_mode: + * @layout: a #ClutterTableLayout + * + * Retrieves the easing mode set using clutter_table_layout_set_easing_mode() + * + * Return value: an easing mode + * + * Since: 1.4 + */ +gulong +clutter_table_layout_get_easing_mode (ClutterTableLayout *layout) +{ + g_return_val_if_fail (CLUTTER_IS_TABLE_LAYOUT (layout), + CLUTTER_EASE_OUT_CUBIC); + + return layout->priv->easing_mode; +} + +/** + * clutter_table_layout_set_easing_duration: + * @layout: a #ClutterTableLayout + * @msecs: the duration of the animations, in milliseconds + * + * Sets the duration of the animations used by @layout when animating changes + * in the layout properties + * + * Use clutter_table_layout_set_use_animations() to enable and disable the + * animations + * + * Since: 1.4 + */ +void +clutter_table_layout_set_easing_duration (ClutterTableLayout *layout, + guint msecs) +{ + ClutterTableLayoutPrivate *priv; + + g_return_if_fail (CLUTTER_IS_TABLE_LAYOUT (layout)); + + priv = layout->priv; + + if (priv->easing_duration != msecs) + { + priv->easing_duration = msecs; + + g_object_notify (G_OBJECT (layout), "easing-duration"); + } +} + +/** + * clutter_table_layout_get_easing_duration: + * @layout: a #ClutterTableLayout + * + * Retrieves the duration set using clutter_table_layout_set_easing_duration() + * + * Return value: the duration of the animations, in milliseconds + * + * Since: 1.4 + */ +guint +clutter_table_layout_get_easing_duration (ClutterTableLayout *layout) +{ + g_return_val_if_fail (CLUTTER_IS_TABLE_LAYOUT (layout), 500); + + return layout->priv->easing_duration; +} + + +/** + * clutter_table_layout_get_row_count: + * @layout: A #ClutterTableLayout + * + * Retrieve the current number rows in the @layout + * + * Returns: the number of rows + * + * Since: 1.4 + */ +gint +clutter_table_layout_get_row_count (ClutterTableLayout *layout) +{ + g_return_val_if_fail (CLUTTER_IS_TABLE_LAYOUT (layout), -1); + + update_row_col (layout, layout->priv->container); + return CLUTTER_TABLE_LAYOUT (layout)->priv->n_rows; +} + +/** + * clutter_table_layout_get_column_count: + * @layout: A #ClutterTableLayout + * + * Retrieve the current number of columns in @layout + * + * Returns: the number of columns + * + * Since: 1.4 + */ +gint +clutter_table_layout_get_column_count (ClutterTableLayout *layout) +{ + g_return_val_if_fail (CLUTTER_IS_TABLE_LAYOUT (layout), -1); + + update_row_col (layout, layout->priv->container); + return CLUTTER_TABLE_LAYOUT (layout)->priv->n_cols; +} diff --git a/clutter/clutter-table-layout.h b/clutter/clutter-table-layout.h new file mode 100644 index 000000000..bf7006abc --- /dev/null +++ b/clutter/clutter-table-layout.h @@ -0,0 +1,163 @@ +/* + * Clutter. + * + * An OpenGL based 'interactive canvas' library. + * + * Copyright (C) 2010 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: + * Jose Dapena Paz + * + * Based on the MX MxTable actor by: + * Thomas Wood + */ + +#if !defined(__CLUTTER_H_INSIDE__) && !defined(CLUTTER_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __CLUTTER_TABLE_LAYOUT_H__ +#define __CLUTTER_TABLE_LAYOUT_H__ + +#include + +G_BEGIN_DECLS + +#define CLUTTER_TYPE_TABLE_LAYOUT (clutter_table_layout_get_type ()) +#define CLUTTER_TABLE_LAYOUT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CLUTTER_TYPE_TABLE_LAYOUT, ClutterTableLayout)) +#define CLUTTER_IS_TABLE_LAYOUT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CLUTTER_TYPE_TABLE_LAYOUT)) +#define CLUTTER_TABLE_LAYOUT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CLUTTER_TYPE_TABLE_LAYOUT, ClutterTableLayoutClass)) +#define CLUTTER_IS_TABLE_LAYOUT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CLUTTER_TYPE_TABLE_LAYOUT)) +#define CLUTTER_TABLE_LAYOUT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CLUTTER_TYPE_TABLE_LAYOUT, ClutterTableLayoutClass)) + +typedef struct _ClutterTableLayout ClutterTableLayout; +typedef struct _ClutterTableLayoutPrivate ClutterTableLayoutPrivate; +typedef struct _ClutterTableLayoutClass ClutterTableLayoutClass; + +/** + * ClutterTableAlignment: + * @CLUTTER_TABLE_ALIGNMENT_START: Align the child to the top or to the + * left of a cell in the table, depending on the axis + * @CLUTTER_TABLE_ALIGNMENT_CENTER: Align the child to the center of + * a cell in the table + * @CLUTTER_TABLE_ALIGNMENT_END: Align the child to the bottom or to the + * right of a cell in the table, depending on the axis + * + * The alignment policies available on each axis of the #ClutterTableLayout + * + * Since: 1.4 + */ +typedef enum { + CLUTTER_TABLE_ALIGNMENT_START, + CLUTTER_TABLE_ALIGNMENT_CENTER, + CLUTTER_TABLE_ALIGNMENT_END +} ClutterTableAlignment; + +/** + * ClutterTableLayout: + * + * The #ClutterTableLayout structure contains only private data + * and should be accessed using the provided API + * + * Since: 1.4 + */ +struct _ClutterTableLayout +{ + /*< private >*/ + ClutterLayoutManager parent_instance; + + ClutterTableLayoutPrivate *priv; +}; + +/** + * ClutterTableLayoutClass: + * + * The #ClutterTableLayoutClass structure contains only private + * data and should be accessed using the provided API + * + * Since: 1.4 + */ +struct _ClutterTableLayoutClass +{ + /*< private >*/ + ClutterLayoutManagerClass parent_class; +}; + +GType clutter_table_layout_get_type (void) G_GNUC_CONST; + +ClutterLayoutManager *clutter_table_layout_new (void); + +void clutter_table_layout_pack (ClutterTableLayout *layout, + ClutterActor *actor, + gint row, + gint column); + +void clutter_table_layout_set_column_spacing (ClutterTableLayout *layout, + guint spacing); +void clutter_table_layout_set_row_spacing (ClutterTableLayout *layout, + guint spacing); +guint clutter_table_layout_get_column_spacing (ClutterTableLayout *layout); +guint clutter_table_layout_get_row_spacing (ClutterTableLayout *layout); + +void clutter_table_layout_set_span (ClutterTableLayout *layout, + ClutterActor *actor, + gint column_span, + gint row_span); +void clutter_table_layout_get_span (ClutterTableLayout *layout, + ClutterActor *actor, + gint *column_span, + gint *row_span); +void clutter_table_layout_set_alignment (ClutterTableLayout *layout, + ClutterActor *actor, + ClutterTableAlignment x_align, + ClutterTableAlignment y_align); +void clutter_table_layout_get_alignment (ClutterTableLayout *layout, + ClutterActor *actor, + ClutterTableAlignment *x_align, + ClutterTableAlignment *y_align); +void clutter_table_layout_set_fill (ClutterTableLayout *layout, + ClutterActor *actor, + gboolean x_fill, + gboolean y_fill); +void clutter_table_layout_get_fill (ClutterTableLayout *layout, + ClutterActor *actor, + gboolean *x_fill, + gboolean *y_fill); +void clutter_table_layout_set_expand (ClutterTableLayout *layout, + ClutterActor *actor, + gboolean x_expand, + gboolean y_expand); +void clutter_table_layout_get_expand (ClutterTableLayout *layout, + ClutterActor *actor, + gboolean *x_expand, + gboolean *y_expand); + +gint clutter_table_layout_get_row_count (ClutterTableLayout *layout); +gint clutter_table_layout_get_column_count (ClutterTableLayout *layout); + +void clutter_table_layout_set_use_animations (ClutterTableLayout *layout, + gboolean animate); +gboolean clutter_table_layout_get_use_animations (ClutterTableLayout *layout); +void clutter_table_layout_set_easing_mode (ClutterTableLayout *layout, + gulong mode); +gulong clutter_table_layout_get_easing_mode (ClutterTableLayout *layout); +void clutter_table_layout_set_easing_duration (ClutterTableLayout *layout, + guint msecs); +guint clutter_table_layout_get_easing_duration (ClutterTableLayout *layout); + +G_END_DECLS + +#endif /* __CLUTTER_TABLE_LAYOUT_H__ */ diff --git a/clutter/clutter.h b/clutter/clutter.h index 7fae0cd6c..18145472f 100644 --- a/clutter/clutter.h +++ b/clutter/clutter.h @@ -95,6 +95,7 @@ #include "clutter-stage-manager.h" #include "clutter-stage-window.h" #include "clutter-state.h" +#include "clutter-table-layout.h" #include "clutter-texture.h" #include "clutter-text.h" #include "clutter-timeline.h" diff --git a/doc/reference/clutter/clutter-docs.xml.in b/doc/reference/clutter/clutter-docs.xml.in index 22f9d21e0..fd6ea0cc0 100644 --- a/doc/reference/clutter/clutter-docs.xml.in +++ b/doc/reference/clutter/clutter-docs.xml.in @@ -90,6 +90,7 @@ + diff --git a/doc/reference/clutter/clutter-sections.txt b/doc/reference/clutter/clutter-sections.txt index d92816525..e7859011c 100644 --- a/doc/reference/clutter/clutter-sections.txt +++ b/doc/reference/clutter/clutter-sections.txt @@ -2068,6 +2068,53 @@ ClutterBoxLayoutPrivate clutter_box_layout_get_type +
+clutter-table-layout +ClutterTableAlignment +ClutterTableLayout +ClutterTableLayoutClass +clutter_table_layout_new +clutter_table_layout_set_row_spacing +clutter_table_layout_get_row_spacing +clutter_table_layout_set_column_spacing +clutter_table_layout_get_column_spacing +clutter_table_layout_get_row_count +clutter_table_layout_get_column_count + + +clutter_table_layout_pack + + +clutter_table_layout_set_alignment +clutter_table_layout_get_alignment +clutter_table_layout_set_expand +clutter_table_layout_get_expand +clutter_table_layout_set_fill +clutter_table_layout_get_fill +clutter_table_layout_get_span +clutter_table_layout_set_span + + +clutter_table_layout_set_use_animations +clutter_table_layout_get_use_animations +clutter_table_layout_set_easing_duration +clutter_table_layout_get_easing_duration +clutter_table_layout_set_easing_mode +clutter_table_layout_get_easing_mode + + +CLUTTER_TYPE_TABLE_LAYOUT +CLUTTER_TABLE_LAYOUT +CLUTTER_TABLE_LAYOUT_CLASS +CLUTTER_IS_TABLE_LAYOUT +CLUTTER_IS_TABLE_LAYOUT_CLASS +CLUTTER_TABLE_LAYOUT_GET_CLASS + + +ClutterTableLayoutPrivate +clutter_table_layout_get_type +
+
clutter-animator ClutterAnimator diff --git a/doc/reference/clutter/clutter.types b/doc/reference/clutter/clutter.types index 0541f969f..86b1c698e 100644 --- a/doc/reference/clutter/clutter.types +++ b/doc/reference/clutter/clutter.types @@ -56,6 +56,7 @@ clutter_shader_effect_get_type clutter_stage_get_type clutter_stage_manager_get_type clutter_state_get_type +clutter_table_layout_get_type clutter_text_get_type clutter_texture_get_type clutter_timeline_get_type diff --git a/tests/interactive/.gitignore b/tests/interactive/.gitignore index fa5269a33..ea21db2f1 100644 --- a/tests/interactive/.gitignore +++ b/tests/interactive/.gitignore @@ -71,3 +71,4 @@ /test-scrolling /test-bind /test-cogl-point-sprites +/test-table-layout diff --git a/tests/interactive/Makefile.am b/tests/interactive/Makefile.am index 190436feb..58eeae56e 100644 --- a/tests/interactive/Makefile.am +++ b/tests/interactive/Makefile.am @@ -56,7 +56,8 @@ UNIT_TESTS = \ test-constraints.c \ test-scrolling.c \ test-bind.c \ - test-cogl-point-sprites.c + test-cogl-point-sprites.c \ + test-table-layout.c if X11_TESTS UNIT_TESTS += test-pixmap.c diff --git a/tests/interactive/test-table-layout.c b/tests/interactive/test-table-layout.c new file mode 100644 index 000000000..fe39bc339 --- /dev/null +++ b/tests/interactive/test-table-layout.c @@ -0,0 +1,275 @@ +#include +#include + +#include + +#include +#include +#include "pango/cogl-pango.h" + +#define FONT "Sans 12" + +static void +set_text (ClutterActor *actor, const gchar *text) +{ + GList *children, *l; + + children = clutter_container_get_children (CLUTTER_CONTAINER (actor)); + for (l = children; l; l = g_list_next (l)) { + if (CLUTTER_IS_TEXT (l->data)) { + clutter_text_set_text (CLUTTER_TEXT (l->data), text); + break; + } + } + g_list_free (children); +} + +static void +toggle_expand (ClutterActor *actor, ClutterEvent *event, ClutterBox *box) +{ + gboolean x_expand; + gchar *label; + ClutterLayoutManager *layout = clutter_box_get_layout_manager (box); + + + clutter_layout_manager_child_get (layout, CLUTTER_CONTAINER (box), actor, + "x-expand", &x_expand, + NULL); + + x_expand = !x_expand; + + clutter_layout_manager_child_set (layout, CLUTTER_CONTAINER (box), actor, + "x-expand", x_expand, + "y-expand", x_expand, + NULL); + + label = g_strdup_printf ("Expand = %d", x_expand); + set_text (actor, label); + + g_free (label); +} + +static const gchar * +get_alignment_name (ClutterTableAlignment alignment) +{ + switch (alignment) + { + case CLUTTER_TABLE_ALIGNMENT_START: + return "start"; + + case CLUTTER_TABLE_ALIGNMENT_CENTER: + return "center"; + + case CLUTTER_TABLE_ALIGNMENT_END: + return "end"; + } + + return "undefined"; +} + +static void +randomise_align (ClutterActor *actor, ClutterEvent *event, ClutterBox *box) +{ + ClutterTableAlignment x_align, y_align; + gchar *label; + ClutterLayoutManager *layout; + + layout = clutter_box_get_layout_manager (box); + + x_align = (ClutterTableAlignment) g_random_int_range (0, 3); + y_align = (ClutterTableAlignment) g_random_int_range (0, 3); + + clutter_layout_manager_child_set (layout, CLUTTER_CONTAINER (box), actor, + "x-align", x_align, + "y-align", y_align, + NULL); + + label = g_strdup_printf ("Align (%s, %s)", + get_alignment_name (x_align), + get_alignment_name (y_align)); + set_text (actor, label); + g_free (label); +} + +static void +toggle_visible (ClutterActor *actor, ClutterEvent *event, gpointer userdata) +{ + clutter_actor_hide (actor); +} + +gboolean drag = FALSE; + +static ClutterActor * +create_cell (ClutterActor *actor, const gchar *color_str) +{ + ClutterActor *result; + ClutterActor *rectangle; + ClutterColor color; + + result = + clutter_box_new (clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_FILL, + CLUTTER_BIN_ALIGNMENT_FILL)); + + rectangle = clutter_rectangle_new (); + clutter_color_from_string (&color, color_str); + clutter_rectangle_set_color (CLUTTER_RECTANGLE (rectangle), (const ClutterColor *) &color); + clutter_color_from_string (&color, "#000f"); + clutter_rectangle_set_border_color (CLUTTER_RECTANGLE (rectangle), (const ClutterColor *) &color); + clutter_rectangle_set_border_width (CLUTTER_RECTANGLE (rectangle), 2); + + clutter_actor_show (rectangle); + clutter_actor_set_reactive (result, TRUE); + clutter_container_add_actor (CLUTTER_CONTAINER (result), rectangle); + clutter_box_pack (CLUTTER_BOX (result), actor, + "x-align", CLUTTER_BIN_ALIGNMENT_CENTER, + "y-align", CLUTTER_BIN_ALIGNMENT_CENTER, + NULL); + + return result; +} + +static ClutterActor * +create_text (const gchar *label, const gchar *color) +{ + ClutterActor *text; + ClutterActor *result; + + text = clutter_text_new_with_text (FONT, label); + clutter_actor_show (text); + + result = create_cell (text, color); + clutter_actor_show (result); + + return result; +} + +static ClutterActor * +create_image (const gchar *file, const gchar *color) +{ + ClutterActor *texture; + ClutterActor *result; + + texture = clutter_texture_new_from_file (file, NULL); + g_object_set (G_OBJECT (texture), "keep-aspect-ratio", TRUE, NULL); + clutter_actor_show (texture); + + result = create_cell (texture, color); + clutter_actor_show (result); + + return result; +} + +G_MODULE_EXPORT int +test_table_layout_main (int argc, char *argv[]) +{ + ClutterActor *stage; + ClutterLayoutManager *layout; + ClutterActor *actor1, *actor2, *actor3, *actor4, *actor5, *actor6, *actor7, *actor8, *actor9, *actor10; + ClutterActor *box; + gchar *file; + + clutter_init (&argc, &argv); + + stage = clutter_stage_get_default (); + clutter_stage_set_title (CLUTTER_STAGE (stage), "Table Layout"); + clutter_stage_set_user_resizable (CLUTTER_STAGE (stage), TRUE); + clutter_actor_set_size (stage, 640, 480); + + layout = clutter_table_layout_new (); + clutter_table_layout_set_column_spacing (CLUTTER_TABLE_LAYOUT (layout), 10); + clutter_table_layout_set_row_spacing (CLUTTER_TABLE_LAYOUT (layout), 10); + + box = clutter_box_new (layout); + clutter_container_add_actor (CLUTTER_CONTAINER (stage), box); + clutter_actor_add_constraint (box, clutter_bind_constraint_new (stage, CLUTTER_BIND_WIDTH, -10.0)); + clutter_actor_add_constraint (box, clutter_bind_constraint_new (stage, CLUTTER_BIND_HEIGHT, -10.0)); + + actor1 = create_text ("label 1", "#f66f"); + file = g_build_filename (TESTS_DATADIR, "redhand.png", NULL); + actor2 = create_image (file, "#bbcf"); + g_free (file); + actor3 = create_text ("label 3", "#6f6f"); + actor4 = create_text ("Expand = 1", "#66ff"); + actor5 = create_text ("label 5", "#f6ff"); + actor6 = create_text ("label 6", "#6fff"); + actor7 = create_text ("Align (center, center)", "#66ff"); + actor8 = create_text ("label 8", "#ffff"); + actor9 = create_text ("label 9", "#666f"); + actor10 = create_text ("label 10", "#aaaf"); + + clutter_table_layout_pack (CLUTTER_TABLE_LAYOUT (layout), actor1, 0, 0); + clutter_table_layout_pack (CLUTTER_TABLE_LAYOUT (layout), actor2, 0, 1); + clutter_table_layout_pack (CLUTTER_TABLE_LAYOUT (layout), actor3, 1, 1); + clutter_table_layout_pack (CLUTTER_TABLE_LAYOUT (layout), actor4, 2, 0); + clutter_table_layout_pack (CLUTTER_TABLE_LAYOUT (layout), actor5, 3, 0); + clutter_table_layout_pack (CLUTTER_TABLE_LAYOUT (layout), actor6, 3, 1); + clutter_table_layout_pack (CLUTTER_TABLE_LAYOUT (layout), actor7, 4, 1); + clutter_table_layout_pack (CLUTTER_TABLE_LAYOUT (layout), actor8, 4, 0); + clutter_table_layout_pack (CLUTTER_TABLE_LAYOUT (layout), actor9, 5, 0); + clutter_table_layout_pack (CLUTTER_TABLE_LAYOUT (layout), actor10, -1, 0); + clutter_table_layout_set_span (CLUTTER_TABLE_LAYOUT (layout), actor1, 2, 1); + clutter_table_layout_set_span (CLUTTER_TABLE_LAYOUT (layout), actor7, 2, 1); + clutter_table_layout_set_span (CLUTTER_TABLE_LAYOUT (layout), actor4, 1, 2); + + clutter_actor_set_size (actor1, 100, 100); + clutter_actor_set_width (actor4, 250); + + clutter_layout_manager_child_set (CLUTTER_LAYOUT_MANAGER (layout), + CLUTTER_CONTAINER (box), + actor1, + "x-expand", FALSE, "y-expand", FALSE, + NULL); + clutter_layout_manager_child_set (CLUTTER_LAYOUT_MANAGER (layout), + CLUTTER_CONTAINER (box), + actor4, + "x-expand", TRUE, "y-expand", TRUE, + "x-fill", TRUE, "y-fill", TRUE, + NULL); + clutter_layout_manager_child_set (CLUTTER_LAYOUT_MANAGER (layout), + CLUTTER_CONTAINER (box), + actor7, + "x-expand", TRUE, "y-expand", TRUE, + "x-fill", FALSE, "y-fill", FALSE, + NULL); + clutter_layout_manager_child_set (CLUTTER_LAYOUT_MANAGER (layout), + CLUTTER_CONTAINER (box), + actor8, + "x-expand", FALSE, "y-expand", FALSE, + NULL); + clutter_layout_manager_child_set (CLUTTER_LAYOUT_MANAGER (layout), + CLUTTER_CONTAINER (box), + actor9, + "x-expand", FALSE, "y-expand", FALSE, + NULL); + + clutter_layout_manager_child_set (CLUTTER_LAYOUT_MANAGER (layout), + CLUTTER_CONTAINER (box), + actor2, + "y-fill", FALSE, + "x-fill", FALSE, + NULL); + + clutter_actor_set_position (box, 5, 5); + + g_signal_connect (actor4, "button-release-event", G_CALLBACK (toggle_expand), box); + g_signal_connect (actor7, "button-release-event", G_CALLBACK (randomise_align), box); + g_signal_connect (actor10, "button-release-event", G_CALLBACK (toggle_visible), NULL); + + /* g_signal_connect (stage, "button-press-event", G_CALLBACK (button_press), */ + /* box); */ + /* g_signal_connect (stage, "motion-event", G_CALLBACK (motion_event), */ + /* box); */ + /* g_signal_connect (stage, "button-release-event", G_CALLBACK (button_release), */ + /* box); */ + + clutter_actor_show (stage); + + g_debug ("table row count = %d", + clutter_table_layout_get_row_count (CLUTTER_TABLE_LAYOUT (layout))); + g_debug ("table column count = %d", + clutter_table_layout_get_column_count (CLUTTER_TABLE_LAYOUT (layout))); + + clutter_main (); + + return EXIT_SUCCESS; +}