/* * Clutter. * * An OpenGL based 'interactive canvas' library. * * Copyright (C) 2009 Intel Corporation. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Author: * Emmanuele Bassi */ /** * SECTION:clutter-flow-layout * @short_description: A reflowing layout manager * * #ClutterFlowLayout is a layout manager which implements the following * policy: * * - the preferred natural size depends on the value * of the #ClutterFlowLayout:orientation property; the layout will try * to maintain all its children on a single row or * column; * - if either the width or the height allocated are * smaller than the preferred ones, the layout will wrap; in this case, * the preferred height or width, respectively, will take into account * the amount of columns and rows; * - each line (either column or row) in reflowing will * have the size of the biggest cell on that line; if the * #ClutterFlowLayout:homogeneous property is set to %FALSE the actor * will be allocated within that area, and if set to %TRUE instead the * actor will be given exactly that area; * - the size of the columns or rows can be controlled * for both minimum and maximum; the spacing can also be controlled * in both columns and rows. * * The [flow-layout example](https://git.gnome.org/browse/clutter/tree/examples/flow-layout.c?h=clutter-1.18) * shows how to use the #ClutterFlowLayout. * * #ClutterFlowLayout is available since Clutter 1.2 */ #ifdef HAVE_CONFIG_H #include "clutter-build-config.h" #endif #include #define CLUTTER_DISABLE_DEPRECATION_WARNINGS #include "deprecated/clutter-container.h" #include "clutter-actor.h" #include "clutter-animatable.h" #include "clutter-child-meta.h" #include "clutter-debug.h" #include "clutter-enum-types.h" #include "clutter-flow-layout.h" #include "clutter-layout-meta.h" #include "clutter-private.h" struct _ClutterFlowLayoutPrivate { ClutterContainer *container; ClutterFlowOrientation orientation; gfloat col_spacing; gfloat row_spacing; gfloat min_col_width; gfloat max_col_width; gfloat col_width; gfloat min_row_height; gfloat max_row_height; gfloat row_height; /* per-line size */ GArray *line_min; GArray *line_natural; gfloat req_width; gfloat req_height; guint line_count; guint is_homogeneous : 1; guint snap_to_grid : 1; }; enum { PROP_0, PROP_ORIENTATION, PROP_HOMOGENEOUS, PROP_COLUMN_SPACING, PROP_ROW_SPACING, PROP_MIN_COLUMN_WIDTH, PROP_MAX_COLUMN_WIDTH, PROP_MIN_ROW_HEGHT, PROP_MAX_ROW_HEIGHT, PROP_SNAP_TO_GRID, N_PROPERTIES }; static GParamSpec *flow_properties[N_PROPERTIES] = { NULL, }; G_DEFINE_TYPE_WITH_PRIVATE (ClutterFlowLayout, clutter_flow_layout, CLUTTER_TYPE_LAYOUT_MANAGER) static gint get_columns (ClutterFlowLayout *self, gfloat for_width) { ClutterFlowLayoutPrivate *priv = self->priv; gint n_columns; if (for_width < 0) return 1; if (priv->col_width == 0) return 1; n_columns = (gint) (for_width + priv->col_spacing) / (priv->col_width + priv->col_spacing); if (n_columns == 0) return 1; return n_columns; } static gint get_rows (ClutterFlowLayout *self, gfloat for_height) { ClutterFlowLayoutPrivate *priv = self->priv; gint n_rows; if (for_height < 0) return 1; if (priv->row_height == 0) return 1; n_rows = (gint) (for_height + priv->row_spacing) / (priv->row_height + priv->row_spacing); if (n_rows == 0) return 1; return n_rows; } static gint compute_lines (ClutterFlowLayout *self, gfloat avail_width, gfloat avail_height) { ClutterFlowLayoutPrivate *priv = self->priv; if (priv->orientation == CLUTTER_FLOW_HORIZONTAL) return get_columns (self, avail_width); else return get_rows (self, avail_height); } static void clutter_flow_layout_get_preferred_width (ClutterLayoutManager *manager, ClutterContainer *container, gfloat for_height, gfloat *min_width_p, gfloat *nat_width_p) { ClutterFlowLayoutPrivate *priv = CLUTTER_FLOW_LAYOUT (manager)->priv; gint n_rows, line_item_count, line_count; gfloat total_min_width, total_natural_width; gfloat line_min_width, line_natural_width; gfloat max_min_width, max_natural_width; ClutterActor *actor, *child; ClutterActorIter iter; gfloat item_y; n_rows = get_rows (CLUTTER_FLOW_LAYOUT (manager), for_height); total_min_width = 0; total_natural_width = 0; line_min_width = 0; line_natural_width = 0; line_item_count = 0; line_count = 0; item_y = 0; actor = CLUTTER_ACTOR (container); /* clear the line width arrays */ if (priv->line_min != NULL) g_array_free (priv->line_min, TRUE); if (priv->line_natural != NULL) g_array_free (priv->line_natural, TRUE); priv->line_min = g_array_sized_new (FALSE, FALSE, sizeof (gfloat), 16); priv->line_natural = g_array_sized_new (FALSE, FALSE, sizeof (gfloat), 16); if (clutter_actor_get_n_children (actor) != 0) line_count = 1; max_min_width = max_natural_width = 0; clutter_actor_iter_init (&iter, actor); while (clutter_actor_iter_next (&iter, &child)) { gfloat child_min, child_natural; gfloat new_y, item_height; if (!clutter_actor_is_visible (child)) continue; if (priv->orientation == CLUTTER_FLOW_VERTICAL && for_height > 0) { clutter_actor_get_preferred_height (child, -1, &child_min, &child_natural); if ((priv->snap_to_grid && line_item_count == n_rows) || (!priv->snap_to_grid && item_y + child_natural > for_height)) { total_min_width += line_min_width; total_natural_width += line_natural_width; g_array_append_val (priv->line_min, line_min_width); g_array_append_val (priv->line_natural, line_natural_width); line_min_width = line_natural_width = 0; line_item_count = 0; line_count += 1; item_y = 0; } if (priv->snap_to_grid) { new_y = ((line_item_count + 1) * (for_height + priv->row_spacing)) / n_rows; item_height = new_y - item_y - priv->row_spacing; } else { new_y = item_y + child_natural + priv->row_spacing; item_height = child_natural; } clutter_actor_get_preferred_width (child, item_height, &child_min, &child_natural); line_min_width = MAX (line_min_width, child_min); line_natural_width = MAX (line_natural_width, child_natural); item_y = new_y; line_item_count += 1; max_min_width = MAX (max_min_width, line_min_width); max_natural_width = MAX (max_natural_width, line_natural_width); } else { clutter_actor_get_preferred_width (child, for_height, &child_min, &child_natural); max_min_width = MAX (max_min_width, child_min); max_natural_width = MAX (max_natural_width, child_natural); total_min_width += max_min_width; total_natural_width += max_natural_width; line_count += 1; } } priv->col_width = max_natural_width; if (priv->max_col_width > 0 && priv->col_width > priv->max_col_width) priv->col_width = MAX (priv->max_col_width, max_min_width); if (priv->col_width < priv->min_col_width) priv->col_width = priv->min_col_width; if (priv->orientation == CLUTTER_FLOW_VERTICAL && for_height > 0) { /* if we have a non-full row we need to add it */ if (line_item_count > 0) { total_min_width += line_min_width; total_natural_width += line_natural_width; g_array_append_val (priv->line_min, line_min_width); g_array_append_val (priv->line_natural, line_natural_width); } priv->line_count = line_count; if (priv->line_count > 0) { gfloat total_spacing; total_spacing = priv->col_spacing * (priv->line_count - 1); total_min_width += total_spacing; total_natural_width += total_spacing; } } else { g_array_append_val (priv->line_min, line_min_width); g_array_append_val (priv->line_natural, line_natural_width); priv->line_count = line_count; if (priv->line_count > 0) { gfloat total_spacing; total_spacing = priv->col_spacing * (priv->line_count - 1); total_min_width += total_spacing; total_natural_width += total_spacing; } } CLUTTER_NOTE (LAYOUT, "Flow[w]: %d lines (%d per line): w [ %.2f, %.2f ] for h %.2f", n_rows, priv->line_count, total_min_width, total_natural_width, for_height); priv->req_height = for_height; if (min_width_p) *min_width_p = max_min_width; if (nat_width_p) *nat_width_p = total_natural_width; } static void clutter_flow_layout_get_preferred_height (ClutterLayoutManager *manager, ClutterContainer *container, gfloat for_width, gfloat *min_height_p, gfloat *nat_height_p) { ClutterFlowLayoutPrivate *priv = CLUTTER_FLOW_LAYOUT (manager)->priv; gint n_columns, line_item_count, line_count; gfloat total_min_height, total_natural_height; gfloat line_min_height, line_natural_height; gfloat max_min_height, max_natural_height; ClutterActor *actor, *child; ClutterActorIter iter; gfloat item_x; n_columns = get_columns (CLUTTER_FLOW_LAYOUT (manager), for_width); total_min_height = 0; total_natural_height = 0; line_min_height = 0; line_natural_height = 0; line_item_count = 0; line_count = 0; item_x = 0; actor = CLUTTER_ACTOR (container); /* clear the line height arrays */ if (priv->line_min != NULL) g_array_free (priv->line_min, TRUE); if (priv->line_natural != NULL) g_array_free (priv->line_natural, TRUE); priv->line_min = g_array_sized_new (FALSE, FALSE, sizeof (gfloat), 16); priv->line_natural = g_array_sized_new (FALSE, FALSE, sizeof (gfloat), 16); if (clutter_actor_get_n_children (actor) != 0) line_count = 1; max_min_height = max_natural_height = 0; clutter_actor_iter_init (&iter, actor); while (clutter_actor_iter_next (&iter, &child)) { gfloat child_min, child_natural; gfloat new_x, item_width; if (!clutter_actor_is_visible (child)) continue; if (priv->orientation == CLUTTER_FLOW_HORIZONTAL && for_width > 0) { clutter_actor_get_preferred_width (child, -1, &child_min, &child_natural); if ((priv->snap_to_grid && line_item_count == n_columns) || (!priv->snap_to_grid && item_x + child_natural > for_width)) { total_min_height += line_min_height; total_natural_height += line_natural_height; g_array_append_val (priv->line_min, line_min_height); g_array_append_val (priv->line_natural, line_natural_height); line_min_height = line_natural_height = 0; line_item_count = 0; line_count += 1; item_x = 0; } if (priv->snap_to_grid) { new_x = ((line_item_count + 1) * (for_width + priv->col_spacing)) / n_columns; item_width = new_x - item_x - priv->col_spacing; } else { new_x = item_x + child_natural + priv->col_spacing; item_width = child_natural; } clutter_actor_get_preferred_height (child, item_width, &child_min, &child_natural); line_min_height = MAX (line_min_height, child_min); line_natural_height = MAX (line_natural_height, child_natural); item_x = new_x; line_item_count += 1; max_min_height = MAX (max_min_height, line_min_height); max_natural_height = MAX (max_natural_height, line_natural_height); } else { clutter_actor_get_preferred_height (child, for_width, &child_min, &child_natural); max_min_height = MAX (max_min_height, child_min); max_natural_height = MAX (max_natural_height, child_natural); total_min_height += max_min_height; total_natural_height += max_natural_height; line_count += 1; } } priv->row_height = max_natural_height; if (priv->max_row_height > 0 && priv->row_height > priv->max_row_height) priv->row_height = MAX (priv->max_row_height, max_min_height); if (priv->row_height < priv->min_row_height) priv->row_height = priv->min_row_height; if (priv->orientation == CLUTTER_FLOW_HORIZONTAL && for_width > 0) { /* if we have a non-full row we need to add it */ if (line_item_count > 0) { total_min_height += line_min_height; total_natural_height += line_natural_height; g_array_append_val (priv->line_min, line_min_height); g_array_append_val (priv->line_natural, line_natural_height); } priv->line_count = line_count; if (priv->line_count > 0) { gfloat total_spacing; total_spacing = priv->row_spacing * (priv->line_count - 1); total_min_height += total_spacing; total_natural_height += total_spacing; } } else { g_array_append_val (priv->line_min, line_min_height); g_array_append_val (priv->line_natural, line_natural_height); priv->line_count = line_count; if (priv->line_count > 0) { gfloat total_spacing; total_spacing = priv->col_spacing * priv->line_count; total_min_height += total_spacing; total_natural_height += total_spacing; } } CLUTTER_NOTE (LAYOUT, "Flow[h]: %d lines (%d per line): w [ %.2f, %.2f ] for h %.2f", n_columns, priv->line_count, total_min_height, total_natural_height, for_width); priv->req_width = for_width; if (min_height_p) *min_height_p = max_min_height; if (nat_height_p) *nat_height_p = total_natural_height; } static void clutter_flow_layout_allocate (ClutterLayoutManager *manager, ClutterContainer *container, const ClutterActorBox *allocation, ClutterAllocationFlags flags) { ClutterFlowLayoutPrivate *priv = CLUTTER_FLOW_LAYOUT (manager)->priv; ClutterActor *actor, *child; ClutterActorIter iter; gfloat x_off, y_off; gfloat avail_width, avail_height; gfloat item_x, item_y; gint line_item_count; gint items_per_line; gint line_index; actor = CLUTTER_ACTOR (container); if (clutter_actor_get_n_children (actor) == 0) return; clutter_actor_box_get_origin (allocation, &x_off, &y_off); clutter_actor_box_get_size (allocation, &avail_width, &avail_height); /* blow the cached preferred size and re-compute with the given * available size in case the FlowLayout wasn't given the exact * size it requested */ if ((priv->req_width >= 0 && avail_width != priv->req_width) || (priv->req_height >= 0 && avail_height != priv->req_height)) { clutter_flow_layout_get_preferred_width (manager, container, avail_height, NULL, NULL); clutter_flow_layout_get_preferred_height (manager, container, avail_width, NULL, NULL); } items_per_line = compute_lines (CLUTTER_FLOW_LAYOUT (manager), avail_width, avail_height); item_x = x_off; item_y = y_off; line_item_count = 0; line_index = 0; clutter_actor_iter_init (&iter, actor); while (clutter_actor_iter_next (&iter, &child)) { ClutterActorBox child_alloc; gfloat item_width, item_height; gfloat new_x, new_y; gfloat child_min, child_natural; if (!clutter_actor_is_visible (child)) continue; new_x = new_y = 0; if (!priv->snap_to_grid) clutter_actor_get_preferred_size (child, NULL, NULL, &item_width, &item_height); if (priv->orientation == CLUTTER_FLOW_HORIZONTAL) { if ((priv->snap_to_grid && line_item_count == items_per_line && line_item_count > 0) || (!priv->snap_to_grid && item_x + item_width > avail_width)) { item_y += g_array_index (priv->line_natural, gfloat, line_index); if (line_index >= 0) item_y += priv->row_spacing; line_item_count = 0; line_index += 1; item_x = x_off; } if (priv->snap_to_grid) { new_x = x_off + ((line_item_count + 1) * (avail_width + priv->col_spacing)) / items_per_line; item_width = new_x - item_x - priv->col_spacing; } else { new_x = item_x + item_width + priv->col_spacing; } item_height = g_array_index (priv->line_natural, gfloat, line_index); } else { if ((priv->snap_to_grid && line_item_count == items_per_line && line_item_count > 0) || (!priv->snap_to_grid && item_y + item_height > avail_height)) { item_x += g_array_index (priv->line_natural, gfloat, line_index); if (line_index >= 0) item_x += priv->col_spacing; line_item_count = 0; line_index += 1; item_y = y_off; } if (priv->snap_to_grid) { new_y = y_off + ((line_item_count + 1) * (avail_height + priv->row_spacing)) / items_per_line; item_height = new_y - item_y - priv->row_spacing; } else { new_y = item_y + item_height + priv->row_spacing; } item_width = g_array_index (priv->line_natural, gfloat, line_index); } if (!priv->is_homogeneous && !clutter_actor_needs_expand (child, CLUTTER_ORIENTATION_HORIZONTAL)) { clutter_actor_get_preferred_width (child, item_height, &child_min, &child_natural); item_width = MIN (item_width, child_natural); } if (!priv->is_homogeneous && !clutter_actor_needs_expand (child, CLUTTER_ORIENTATION_VERTICAL)) { clutter_actor_get_preferred_height (child, item_width, &child_min, &child_natural); item_height = MIN (item_height, child_natural); } CLUTTER_NOTE (LAYOUT, "flow[line:%d, item:%d/%d] =" "{ %.2f, %.2f, %.2f, %.2f }", line_index, line_item_count + 1, items_per_line, item_x, item_y, item_width, item_height); child_alloc.x1 = ceil (item_x); child_alloc.y1 = ceil (item_y); child_alloc.x2 = ceil (child_alloc.x1 + item_width); child_alloc.y2 = ceil (child_alloc.y1 + item_height); clutter_actor_allocate (child, &child_alloc, flags); if (priv->orientation == CLUTTER_FLOW_HORIZONTAL) item_x = new_x; else item_y = new_y; line_item_count += 1; } } static void clutter_flow_layout_set_container (ClutterLayoutManager *manager, ClutterContainer *container) { ClutterFlowLayoutPrivate *priv = CLUTTER_FLOW_LAYOUT (manager)->priv; ClutterLayoutManagerClass *parent_class; priv->container = container; if (priv->container != NULL) { ClutterRequestMode request_mode; /* we need to change the :request-mode of the container * to match the orientation */ request_mode = (priv->orientation == CLUTTER_FLOW_HORIZONTAL) ? CLUTTER_REQUEST_HEIGHT_FOR_WIDTH : CLUTTER_REQUEST_WIDTH_FOR_HEIGHT; clutter_actor_set_request_mode (CLUTTER_ACTOR (priv->container), request_mode); } parent_class = CLUTTER_LAYOUT_MANAGER_CLASS (clutter_flow_layout_parent_class); parent_class->set_container (manager, container); } static void clutter_flow_layout_set_property (GObject *gobject, guint prop_id, const GValue *value, GParamSpec *pspec) { ClutterFlowLayout *self = CLUTTER_FLOW_LAYOUT (gobject); switch (prop_id) { case PROP_ORIENTATION: clutter_flow_layout_set_orientation (self, g_value_get_enum (value)); break; case PROP_HOMOGENEOUS: clutter_flow_layout_set_homogeneous (self, g_value_get_boolean (value)); break; case PROP_COLUMN_SPACING: clutter_flow_layout_set_column_spacing (self, g_value_get_float (value)); break; case PROP_ROW_SPACING: clutter_flow_layout_set_row_spacing (self, g_value_get_float (value)); break; case PROP_MIN_COLUMN_WIDTH: clutter_flow_layout_set_column_width (self, g_value_get_float (value), self->priv->max_col_width); break; case PROP_MAX_COLUMN_WIDTH: clutter_flow_layout_set_column_width (self, self->priv->min_col_width, g_value_get_float (value)); break; case PROP_MIN_ROW_HEGHT: clutter_flow_layout_set_row_height (self, g_value_get_float (value), self->priv->max_row_height); break; case PROP_MAX_ROW_HEIGHT: clutter_flow_layout_set_row_height (self, self->priv->min_row_height, g_value_get_float (value)); break; case PROP_SNAP_TO_GRID: clutter_flow_layout_set_snap_to_grid (self, g_value_get_boolean (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); break; } } static void clutter_flow_layout_get_property (GObject *gobject, guint prop_id, GValue *value, GParamSpec *pspec) { ClutterFlowLayoutPrivate *priv = CLUTTER_FLOW_LAYOUT (gobject)->priv; switch (prop_id) { case PROP_ORIENTATION: g_value_set_enum (value, priv->orientation); break; case PROP_HOMOGENEOUS: g_value_set_boolean (value, priv->is_homogeneous); break; case PROP_COLUMN_SPACING: g_value_set_float (value, priv->col_spacing); break; case PROP_ROW_SPACING: g_value_set_float (value, priv->row_spacing); break; case PROP_MIN_COLUMN_WIDTH: g_value_set_float (value, priv->min_col_width); break; case PROP_MAX_COLUMN_WIDTH: g_value_set_float (value, priv->max_col_width); break; case PROP_MIN_ROW_HEGHT: g_value_set_float (value, priv->min_row_height); break; case PROP_MAX_ROW_HEIGHT: g_value_set_float (value, priv->max_row_height); break; case PROP_SNAP_TO_GRID: g_value_set_boolean (value, priv->snap_to_grid); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); break; } } static void clutter_flow_layout_finalize (GObject *gobject) { ClutterFlowLayoutPrivate *priv = CLUTTER_FLOW_LAYOUT (gobject)->priv; if (priv->line_min != NULL) g_array_free (priv->line_min, TRUE); if (priv->line_natural != NULL) g_array_free (priv->line_natural, TRUE); G_OBJECT_CLASS (clutter_flow_layout_parent_class)->finalize (gobject); } static void clutter_flow_layout_class_init (ClutterFlowLayoutClass *klass) { GObjectClass *gobject_class; ClutterLayoutManagerClass *layout_class; gobject_class = G_OBJECT_CLASS (klass); layout_class = CLUTTER_LAYOUT_MANAGER_CLASS (klass); layout_class->get_preferred_width = clutter_flow_layout_get_preferred_width; layout_class->get_preferred_height = clutter_flow_layout_get_preferred_height; layout_class->allocate = clutter_flow_layout_allocate; layout_class->set_container = clutter_flow_layout_set_container; /** * ClutterFlowLayout:orientation: * * The orientation of the #ClutterFlowLayout. The children * of the layout will be layed out following the orientation. * * This property also controls the overflowing directions * * Since: 1.2 */ flow_properties[PROP_ORIENTATION] = g_param_spec_enum ("orientation", P_("Orientation"), P_("The orientation of the layout"), CLUTTER_TYPE_FLOW_ORIENTATION, CLUTTER_FLOW_HORIZONTAL, CLUTTER_PARAM_READWRITE | G_PARAM_CONSTRUCT); /** * ClutterFlowLayout:homogeneous: * * Whether each child inside the #ClutterFlowLayout should receive * the same allocation * * Since: 1.2 */ flow_properties[PROP_HOMOGENEOUS] = g_param_spec_boolean ("homogeneous", P_("Homogeneous"), P_("Whether each item should receive the same allocation"), FALSE, CLUTTER_PARAM_READWRITE); /** * ClutterFlowLayout:column-spacing: * * The spacing between columns, in pixels; the value of this * property is honoured by horizontal non-overflowing layouts * and by vertical overflowing layouts * * Since: 1.2 */ flow_properties[PROP_COLUMN_SPACING] = g_param_spec_float ("column-spacing", P_("Column Spacing"), P_("The spacing between columns"), 0.0, G_MAXFLOAT, 0.0, CLUTTER_PARAM_READWRITE); /** * ClutterFlowLayout:row-spacing: * * The spacing between rows, in pixels; the value of this * property is honoured by vertical non-overflowing layouts and * by horizontal overflowing layouts * * Since: 1.2 */ flow_properties[PROP_ROW_SPACING] = g_param_spec_float ("row-spacing", P_("Row Spacing"), P_("The spacing between rows"), 0.0, G_MAXFLOAT, 0.0, CLUTTER_PARAM_READWRITE); /** * ClutterFlowLayout:min-column-width: * * Minimum width for each column in the layout, in pixels * * Since: 1.2 */ flow_properties[PROP_MIN_COLUMN_WIDTH] = g_param_spec_float ("min-column-width", P_("Minimum Column Width"), P_("Minimum width for each column"), 0.0, G_MAXFLOAT, 0.0, CLUTTER_PARAM_READWRITE); /** * ClutterFlowLayout:max-column-width: * * Maximum width for each column in the layout, in pixels. If * set to -1 the width will be the maximum child width * * Since: 1.2 */ flow_properties[PROP_MAX_COLUMN_WIDTH] = g_param_spec_float ("max-column-width", P_("Maximum Column Width"), P_("Maximum width for each column"), -1.0, G_MAXFLOAT, -1.0, CLUTTER_PARAM_READWRITE); /** * ClutterFlowLayout:min-row-height: * * Minimum height for each row in the layout, in pixels * * Since: 1.2 */ flow_properties[PROP_MIN_ROW_HEGHT] = g_param_spec_float ("min-row-height", P_("Minimum Row Height"), P_("Minimum height for each row"), 0.0, G_MAXFLOAT, 0.0, CLUTTER_PARAM_READWRITE); /** * ClutterFlowLayout:max-row-height: * * Maximum height for each row in the layout, in pixels. If * set to -1 the width will be the maximum child height * * Since: 1.2 */ flow_properties[PROP_MAX_ROW_HEIGHT] = g_param_spec_float ("max-row-height", P_("Maximum Row Height"), P_("Maximum height for each row"), -1.0, G_MAXFLOAT, -1.0, CLUTTER_PARAM_READWRITE); /** * ClutterFlowLayout:snap-to-grid: * * Whether the #ClutterFlowLayout should arrange its children * on a grid * * Since: 1.16 */ flow_properties[PROP_SNAP_TO_GRID] = g_param_spec_boolean ("snap-to-grid", P_("Snap to grid"), P_("Snap to grid"), TRUE, CLUTTER_PARAM_READWRITE); gobject_class->finalize = clutter_flow_layout_finalize; gobject_class->set_property = clutter_flow_layout_set_property; gobject_class->get_property = clutter_flow_layout_get_property; g_object_class_install_properties (gobject_class, N_PROPERTIES, flow_properties); } static void clutter_flow_layout_init (ClutterFlowLayout *self) { ClutterFlowLayoutPrivate *priv; self->priv = priv = clutter_flow_layout_get_instance_private (self); priv->orientation = CLUTTER_FLOW_HORIZONTAL; priv->col_spacing = 0; priv->row_spacing = 0; priv->min_col_width = priv->min_row_height = 0; priv->max_col_width = priv->max_row_height = -1; priv->line_min = NULL; priv->line_natural = NULL; priv->snap_to_grid = TRUE; } /** * clutter_flow_layout_new: * @orientation: the orientation of the flow layout * * Creates a new #ClutterFlowLayout with the given @orientation * * Return value: the newly created #ClutterFlowLayout * * Since: 1.2 */ ClutterLayoutManager * clutter_flow_layout_new (ClutterFlowOrientation orientation) { return g_object_new (CLUTTER_TYPE_FLOW_LAYOUT, "orientation", orientation, NULL); } /** * clutter_flow_layout_set_orientation: * @layout: a #ClutterFlowLayout * @orientation: the orientation of the layout * * Sets the orientation of the flow layout * * The orientation controls the direction used to allocate * the children: either horizontally or vertically. The * orientation also controls the direction of the overflowing * * Since: 1.2 */ void clutter_flow_layout_set_orientation (ClutterFlowLayout *layout, ClutterFlowOrientation orientation) { ClutterFlowLayoutPrivate *priv; g_return_if_fail (CLUTTER_IS_FLOW_LAYOUT (layout)); priv = layout->priv; if (priv->orientation != orientation) { ClutterLayoutManager *manager; priv->orientation = orientation; if (priv->container != NULL) { ClutterRequestMode request_mode; /* we need to change the :request-mode of the container * to match the orientation */ request_mode = (priv->orientation == CLUTTER_FLOW_HORIZONTAL) ? CLUTTER_REQUEST_HEIGHT_FOR_WIDTH : CLUTTER_REQUEST_WIDTH_FOR_HEIGHT; clutter_actor_set_request_mode (CLUTTER_ACTOR (priv->container), request_mode); } manager = CLUTTER_LAYOUT_MANAGER (layout); clutter_layout_manager_layout_changed (manager); g_object_notify_by_pspec (G_OBJECT (layout), flow_properties[PROP_ORIENTATION]); } } /** * clutter_flow_layout_get_orientation: * @layout: a #ClutterFlowLayout * * Retrieves the orientation of the @layout * * Return value: the orientation of the #ClutterFlowLayout * * Since: 1.2 */ ClutterFlowOrientation clutter_flow_layout_get_orientation (ClutterFlowLayout *layout) { g_return_val_if_fail (CLUTTER_IS_FLOW_LAYOUT (layout), CLUTTER_FLOW_HORIZONTAL); return layout->priv->orientation; } /** * clutter_flow_layout_set_homogeneous: * @layout: a #ClutterFlowLayout * @homogeneous: whether the layout should be homogeneous or not * * Sets whether the @layout should allocate the same space for * each child * * Since: 1.2 */ void clutter_flow_layout_set_homogeneous (ClutterFlowLayout *layout, gboolean homogeneous) { ClutterFlowLayoutPrivate *priv; g_return_if_fail (CLUTTER_IS_FLOW_LAYOUT (layout)); priv = layout->priv; if (priv->is_homogeneous != homogeneous) { ClutterLayoutManager *manager; priv->is_homogeneous = homogeneous; manager = CLUTTER_LAYOUT_MANAGER (layout); clutter_layout_manager_layout_changed (manager); g_object_notify_by_pspec (G_OBJECT (layout), flow_properties[PROP_HOMOGENEOUS]); } } /** * clutter_flow_layout_get_homogeneous: * @layout: a #ClutterFlowLayout * * Retrieves whether the @layout is homogeneous * * Return value: %TRUE if the #ClutterFlowLayout is homogeneous * * Since: 1.2 */ gboolean clutter_flow_layout_get_homogeneous (ClutterFlowLayout *layout) { g_return_val_if_fail (CLUTTER_IS_FLOW_LAYOUT (layout), FALSE); return layout->priv->is_homogeneous; } /** * clutter_flow_layout_set_column_spacing: * @layout: a #ClutterFlowLayout * @spacing: the space between columns * * Sets the space between columns, in pixels * * Since: 1.2 */ void clutter_flow_layout_set_column_spacing (ClutterFlowLayout *layout, gfloat spacing) { ClutterFlowLayoutPrivate *priv; g_return_if_fail (CLUTTER_IS_FLOW_LAYOUT (layout)); priv = layout->priv; if (priv->col_spacing != spacing) { ClutterLayoutManager *manager; priv->col_spacing = spacing; manager = CLUTTER_LAYOUT_MANAGER (layout); clutter_layout_manager_layout_changed (manager); g_object_notify_by_pspec (G_OBJECT (layout), flow_properties[PROP_COLUMN_SPACING]); } } /** * clutter_flow_layout_get_column_spacing: * @layout: a #ClutterFlowLayout * * Retrieves the spacing between columns * * Return value: the spacing between columns of the #ClutterFlowLayout, * in pixels * * Since: 1.2 */ gfloat clutter_flow_layout_get_column_spacing (ClutterFlowLayout *layout) { g_return_val_if_fail (CLUTTER_IS_FLOW_LAYOUT (layout), 0.0); return layout->priv->col_spacing; } /** * clutter_flow_layout_set_row_spacing: * @layout: a #ClutterFlowLayout * @spacing: the space between rows * * Sets the spacing between rows, in pixels * * Since: 1.2 */ void clutter_flow_layout_set_row_spacing (ClutterFlowLayout *layout, gfloat spacing) { ClutterFlowLayoutPrivate *priv; g_return_if_fail (CLUTTER_IS_FLOW_LAYOUT (layout)); priv = layout->priv; if (priv->row_spacing != spacing) { ClutterLayoutManager *manager; priv->row_spacing = spacing; manager = CLUTTER_LAYOUT_MANAGER (layout); clutter_layout_manager_layout_changed (manager); g_object_notify_by_pspec (G_OBJECT (layout), flow_properties[PROP_ROW_SPACING]); } } /** * clutter_flow_layout_get_row_spacing: * @layout: a #ClutterFlowLayout * * Retrieves the spacing between rows * * Return value: the spacing between rows of the #ClutterFlowLayout, * in pixels * * Since: 1.2 */ gfloat clutter_flow_layout_get_row_spacing (ClutterFlowLayout *layout) { g_return_val_if_fail (CLUTTER_IS_FLOW_LAYOUT (layout), 0.0); return layout->priv->row_spacing; } /** * clutter_flow_layout_set_column_width: * @layout: a #ClutterFlowLayout * @min_width: minimum width of a column * @max_width: maximum width of a column * * Sets the minimum and maximum widths that a column can have * * Since: 1.2 */ void clutter_flow_layout_set_column_width (ClutterFlowLayout *layout, gfloat min_width, gfloat max_width) { ClutterFlowLayoutPrivate *priv; gboolean notify_min = FALSE, notify_max = FALSE; g_return_if_fail (CLUTTER_IS_FLOW_LAYOUT (layout)); priv = layout->priv; if (priv->min_col_width != min_width) { priv->min_col_width = min_width; notify_min = TRUE; } if (priv->max_col_width != max_width) { priv->max_col_width = max_width; notify_max = TRUE; } g_object_freeze_notify (G_OBJECT (layout)); if (notify_min || notify_max) { ClutterLayoutManager *manager = CLUTTER_LAYOUT_MANAGER (layout); clutter_layout_manager_layout_changed (manager); } if (notify_min) g_object_notify_by_pspec (G_OBJECT (layout), flow_properties[PROP_MIN_COLUMN_WIDTH]); if (notify_max) g_object_notify_by_pspec (G_OBJECT (layout), flow_properties[PROP_MAX_COLUMN_WIDTH]); g_object_thaw_notify (G_OBJECT (layout)); } /** * clutter_flow_layout_get_column_width: * @layout: a #ClutterFlowLayout * @min_width: (out): return location for the minimum column width, or %NULL * @max_width: (out): return location for the maximum column width, or %NULL * * Retrieves the minimum and maximum column widths * * Since: 1.2 */ void clutter_flow_layout_get_column_width (ClutterFlowLayout *layout, gfloat *min_width, gfloat *max_width) { g_return_if_fail (CLUTTER_IS_FLOW_LAYOUT (layout)); if (min_width) *min_width = layout->priv->min_col_width; if (max_width) *max_width = layout->priv->max_col_width; } /** * clutter_flow_layout_set_row_height: * @layout: a #ClutterFlowLayout * @min_height: the minimum height of a row * @max_height: the maximum height of a row * * Sets the minimum and maximum heights that a row can have * * Since: 1.2 */ void clutter_flow_layout_set_row_height (ClutterFlowLayout *layout, gfloat min_height, gfloat max_height) { ClutterFlowLayoutPrivate *priv; gboolean notify_min = FALSE, notify_max = FALSE; g_return_if_fail (CLUTTER_IS_FLOW_LAYOUT (layout)); priv = layout->priv; if (priv->min_row_height != min_height) { priv->min_row_height = min_height; notify_min = TRUE; } if (priv->max_row_height != max_height) { priv->max_row_height = max_height; notify_max = TRUE; } g_object_freeze_notify (G_OBJECT (layout)); if (notify_min || notify_max) { ClutterLayoutManager *manager = CLUTTER_LAYOUT_MANAGER (layout); clutter_layout_manager_layout_changed (manager); } if (notify_min) g_object_notify_by_pspec (G_OBJECT (layout), flow_properties[PROP_MIN_ROW_HEGHT]); if (notify_max) g_object_notify_by_pspec (G_OBJECT (layout), flow_properties[PROP_MAX_ROW_HEIGHT]); g_object_thaw_notify (G_OBJECT (layout)); } /** * clutter_flow_layout_get_row_height: * @layout: a #ClutterFlowLayout * @min_height: (out): return location for the minimum row height, or %NULL * @max_height: (out): return location for the maximum row height, or %NULL * * Retrieves the minimum and maximum row heights * * Since: 1.2 */ void clutter_flow_layout_get_row_height (ClutterFlowLayout *layout, gfloat *min_height, gfloat *max_height) { g_return_if_fail (CLUTTER_IS_FLOW_LAYOUT (layout)); if (min_height) *min_height = layout->priv->min_row_height; if (max_height) *max_height = layout->priv->max_row_height; } /** * clutter_flow_layout_set_snap_to_grid: * @layout: a #ClutterFlowLayout * @snap_to_grid: %TRUE if @layout should place its children on a grid * * Whether the @layout should place its children on a grid. * * Since: 1.16 */ void clutter_flow_layout_set_snap_to_grid (ClutterFlowLayout *layout, gboolean snap_to_grid) { ClutterFlowLayoutPrivate *priv; g_return_if_fail (CLUTTER_IS_FLOW_LAYOUT (layout)); priv = layout->priv; if (priv->snap_to_grid != snap_to_grid) { priv->snap_to_grid = snap_to_grid; clutter_layout_manager_layout_changed (CLUTTER_LAYOUT_MANAGER (layout)); g_object_notify_by_pspec (G_OBJECT (layout), flow_properties[PROP_SNAP_TO_GRID]); } } /** * clutter_flow_layout_get_snap_to_grid: * @layout: a #ClutterFlowLayout * * Retrieves the value of #ClutterFlowLayout:snap-to-grid property * * Return value: %TRUE if the @layout is placing its children on a grid * * Since: 1.16 */ gboolean clutter_flow_layout_get_snap_to_grid (ClutterFlowLayout *layout) { g_return_val_if_fail (CLUTTER_IS_FLOW_LAYOUT (layout), FALSE); return layout->priv->snap_to_grid; }