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] 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; +}