mutter/clutter/clutter-grid-layout.c
Bastian Winkler 1eb869ec8f Add ClutterGridLayout
ClutterGridLayout is port of GtkGrid to a Clutter layout manager. All
the logic is taken from gtkgrid.c, so all the credits should go to
Matthias Clasen for writing this nice piece of code.

ClutterGridLayout supports adding children with it's own methods
GridLayout.attach() and GridLayout.attach_next_to() as well as
Actor.add_child() and friends. The latter adds children in a similar
fashion to ClutterBoxLayout

https://bugzilla.gnome.org/show_bug.cgi?id=677372
2012-06-05 12:28:25 +02:00

2200 lines
63 KiB
C

/*
* Clutter.
*
* An OpenGL based 'interactive canvas' library.
*
* Copyright (C) 2010 Red Hat, Inc.
* Copyright (C) 2012 Bastian Winkler <buz@netbuz.org>
*
* 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 <http://www.gnu.org/licenses/>.
*
* Author:
* Bastian Winkler <buz@netbuz.org>
*
* Based on GtkGrid widget by:
* Matthias Clasen
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <string.h>
#include <math.h>
#include "clutter-grid-layout.h"
#include "clutter-actor-private.h"
#include "clutter-container.h"
#include "clutter-debug.h"
#include "clutter-enum-types.h"
#include "clutter-layout-meta.h"
#include "clutter-private.h"
/**
* SECTION:clutter-grid-layout
* @Short_description: Pack widgets in a rows and columns
* @Title: ClutterGridLayout
* @See_also: #ClutterTableLayout, #ClutterBoxLayout
*
* #ClutterGridLayout is a layout manager which arranges its child widgets in
* rows and columns. It is a very similar to #ClutterTableLayout and
* #ClutterBoxLayout, but it consistently uses #ClutterActor's
* alignment and expansion flags instead of custom child properties.
*
* Children are added using clutter_grid_layout_attach(). They can span
* multiple rows or columns. It is also possible to add a child next to an
* existing child, using clutter_grid_layout_attach_next_to(). The behaviour of
* #ClutterGridLayout when several children occupy the same grid cell is undefined.
*
* #ClutterGridLayout can be used like a #ClutterBoxLayout by just using
* clutter_actor_add_child(), which will place children next to each other in
* the direction determined by the #ClutterGridLayout:orientation property.
*/
#define CLUTTER_TYPE_GRID_CHILD (clutter_grid_child_get_type ())
#define CLUTTER_GRID_CHILD(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CLUTTER_TYPE_GRID_CHILD, ClutterGridChild))
#define CLUTTER_IS_GRID_CHILD(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CLUTTER_TYPE_GRID_CHILD))
#define CLUTTER_GRID_LAYOUT_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLUTTER_TYPE_GRID_LAYOUT, ClutterGridLayoutPrivate))
typedef struct _ClutterGridChild ClutterGridChild;
typedef struct _ClutterLayoutMetaClass ClutterGridChildClass;
typedef struct _ClutterGridAttach ClutterGridAttach;
typedef struct _ClutterGridLine ClutterGridLine;
typedef struct _ClutterGridLines ClutterGridLines;
typedef struct _ClutterGridLineData ClutterGridLineData;
typedef struct _ClutterGridRequest ClutterGridRequest;
struct _ClutterGridAttach
{
gint pos;
gint span;
};
struct _ClutterGridChild
{
ClutterLayoutMeta parent_instance;
ClutterGridAttach attach[2];
};
#define CHILD_LEFT(child) ((child)->attach[CLUTTER_ORIENTATION_HORIZONTAL].pos)
#define CHILD_WIDTH(child) ((child)->attach[CLUTTER_ORIENTATION_HORIZONTAL].span)
#define CHILD_TOP(child) ((child)->attach[CLUTTER_ORIENTATION_VERTICAL].pos)
#define CHILD_HEIGHT(child) ((child)->attach[CLUTTER_ORIENTATION_VERTICAL].span)
/* A ClutterGridLineData struct contains row/column specific parts
* of the grid.
*/
struct _ClutterGridLineData
{
gfloat spacing;
guint homogeneous : 1;
};
struct _ClutterGridLayoutPrivate
{
ClutterContainer *container;
ClutterOrientation orientation;
ClutterGridLineData linedata[2];
};
#define ROWS(priv) (&(priv)->linedata[CLUTTER_ORIENTATION_HORIZONTAL])
#define COLUMNS(priv) (&(priv)->linedata[CLUTTER_ORIENTATION_VERTICAL])
/* A ClutterGridLine struct represents a single row or column
* during size requests
*/
struct _ClutterGridLine
{
gfloat minimum;
gfloat natural;
gfloat position;
gfloat allocation;
guint need_expand : 1;
guint expand : 1;
guint empty : 1;
};
struct _ClutterGridLines
{
ClutterGridLine *lines;
gint min, max;
};
struct _ClutterGridRequest
{
ClutterGridLayout *grid;
ClutterGridLines lines[2];
};
enum
{
PROP_0,
PROP_ORIENTATION,
PROP_ROW_SPACING,
PROP_COLUMN_SPACING,
PROP_ROW_HOMOGENEOUS,
PROP_COLUMN_HOMOGENEOUS,
PROP_LAST
};
static GParamSpec *obj_props[PROP_LAST];
enum
{
PROP_CHILD_0,
PROP_CHILD_LEFT_ATTACH,
PROP_CHILD_TOP_ATTACH,
PROP_CHILD_WIDTH,
PROP_CHILD_HEIGHT,
PROP_CHILD_LAST
};
static GParamSpec *child_props[PROP_CHILD_LAST];
GType clutter_grid_child_get_type (void);
G_DEFINE_TYPE (ClutterGridChild, clutter_grid_child,
CLUTTER_TYPE_LAYOUT_META);
G_DEFINE_TYPE (ClutterGridLayout, clutter_grid_layout,
CLUTTER_TYPE_LAYOUT_MANAGER);
#define GET_GRID_CHILD(grid, child) \
(CLUTTER_GRID_CHILD(clutter_layout_manager_get_child_meta \
(CLUTTER_LAYOUT_MANAGER((grid)),\
CLUTTER_GRID_LAYOUT((grid))->priv->container,(child))))
static void
grid_attach (ClutterGridLayout *self,
ClutterActor *actor,
gint left,
gint top,
gint width,
gint height)
{
ClutterGridChild *grid_child;
grid_child = GET_GRID_CHILD (self, actor);
CHILD_LEFT (grid_child) = left;
CHILD_TOP (grid_child) = top;
CHILD_WIDTH (grid_child) = width;
CHILD_HEIGHT (grid_child) = height;
}
/* Find the position 'touching' existing
* children. @orientation and @max determine
* from which direction to approach (horizontal
* + max = right, vertical + !max = top, etc).
* @op_pos, @op_span determine the rows/columns
* in which the touching has to happen.
*/
static gint
find_attach_position (ClutterGridLayout *self,
ClutterOrientation orientation,
gint op_pos,
gint op_span,
gboolean max)
{
ClutterGridLayoutPrivate *priv = self->priv;
ClutterGridChild *grid_child;
ClutterGridAttach *attach;
ClutterGridAttach *opposite;
ClutterActorIter iter;
ClutterActor *child;
gint pos;
gboolean hit;
if (max)
pos = -G_MAXINT;
else
pos = G_MAXINT;
hit = FALSE;
if (!priv->container)
return -1;
clutter_actor_iter_init (&iter, CLUTTER_ACTOR (priv->container));
while (clutter_actor_iter_next (&iter, &child))
{
grid_child = GET_GRID_CHILD (self, child);
attach = &grid_child->attach[orientation];
opposite = &grid_child->attach[1 - orientation];
/* check if the ranges overlap */
if (opposite->pos <= op_pos + op_span && op_pos <= opposite->pos + opposite->span)
{
hit = TRUE;
if (max)
pos = MAX (pos, attach->pos + attach->span);
else
pos = MIN (pos, attach->pos);
}
}
if (!hit)
pos = 0;
return pos;
}
static void
grid_attach_next_to (ClutterGridLayout *layout,
ClutterActor *child,
ClutterActor *sibling,
ClutterGridPosition side,
gint width,
gint height)
{
ClutterGridChild *grid_sibling;
gint left, top;
if (sibling)
{
grid_sibling = GET_GRID_CHILD (layout, sibling);
switch (side)
{
case CLUTTER_GRID_POSITION_LEFT:
left = CHILD_LEFT (grid_sibling) - width;
top = CHILD_TOP (grid_sibling);
break;
case CLUTTER_GRID_POSITION_RIGHT:
left = CHILD_LEFT (grid_sibling) + CHILD_WIDTH (grid_sibling);
top = CHILD_TOP (grid_sibling);
break;
case CLUTTER_GRID_POSITION_TOP:
left = CHILD_LEFT (grid_sibling);
top = CHILD_TOP (grid_sibling) - height;
break;
case CLUTTER_GRID_POSITION_BOTTOM:
left = CHILD_LEFT (grid_sibling);
top = CHILD_TOP (grid_sibling) + CHILD_HEIGHT (grid_sibling);
break;
default:
g_assert_not_reached ();
}
}
else
{
switch (side)
{
case CLUTTER_GRID_POSITION_LEFT:
left = find_attach_position (layout, CLUTTER_ORIENTATION_HORIZONTAL,
0, height, FALSE);
left -= width;
top = 0;
break;
case CLUTTER_GRID_POSITION_RIGHT:
left = find_attach_position (layout, CLUTTER_ORIENTATION_HORIZONTAL,
0, height, TRUE);
top = 0;
break;
case CLUTTER_GRID_POSITION_TOP:
left = 0;
top = find_attach_position (layout, CLUTTER_ORIENTATION_VERTICAL,
0, width, FALSE);
top -= height;
break;
case CLUTTER_GRID_POSITION_BOTTOM:
left = 0;
top = find_attach_position (layout, CLUTTER_ORIENTATION_VERTICAL,
0, width, TRUE);
break;
default:
g_assert_not_reached ();
}
}
grid_attach (layout, child, left, top, width, height);
}
static void
clutter_grid_request_update_child_attach (ClutterGridRequest *request,
ClutterActor *actor)
{
ClutterGridLayoutPrivate *priv = request->grid->priv;
ClutterGridChild *grid_child;
grid_child = GET_GRID_CHILD (request->grid, actor);
if (CHILD_LEFT (grid_child) == -1 || CHILD_TOP (grid_child) == -1)
{
ClutterGridPosition side;
ClutterActor *sibling;
if (priv->orientation == CLUTTER_ORIENTATION_HORIZONTAL)
{
ClutterTextDirection td;
gboolean rtl;
ClutterActor *container = CLUTTER_ACTOR (priv->container);
td = clutter_actor_get_text_direction (container);
rtl = (td == CLUTTER_TEXT_DIRECTION_RTL) ? TRUE : FALSE;
side = rtl ? CLUTTER_GRID_POSITION_RIGHT : CLUTTER_GRID_POSITION_LEFT;
}
else
{
/* XXX: maybe we should also add a :pack-start property to modify
* this */
side = CLUTTER_GRID_POSITION_BOTTOM;
}
sibling = clutter_actor_get_previous_sibling (actor);
grid_attach_next_to (request->grid, actor, sibling, side,
CHILD_WIDTH (grid_child),
CHILD_HEIGHT (grid_child));
}
}
static void
clutter_grid_request_update_attach (ClutterGridRequest *request)
{
ClutterGridLayoutPrivate *priv = request->grid->priv;
ClutterActorIter iter;
ClutterActor *child;
clutter_actor_iter_init (&iter, CLUTTER_ACTOR (priv->container));
while (clutter_actor_iter_next (&iter, &child))
clutter_grid_request_update_child_attach (request, child);
}
/* Calculates the min and max numbers for both orientations.
*/
static void
clutter_grid_request_count_lines (ClutterGridRequest *request)
{
ClutterGridLayoutPrivate *priv = request->grid->priv;
ClutterGridChild *grid_child;
ClutterGridAttach *attach;
ClutterActorIter iter;
ClutterActor *child;
gint min[2];
gint max[2];
min[0] = min[1] = G_MAXINT;
max[0] = max[1] = G_MININT;
clutter_actor_iter_init (&iter, CLUTTER_ACTOR (priv->container));
while (clutter_actor_iter_next (&iter, &child))
{
grid_child = GET_GRID_CHILD (request->grid, child);
attach = grid_child->attach;
min[0] = MIN (min[0], attach[0].pos);
max[0] = MAX (max[0], attach[0].pos + attach[0].span);
min[1] = MIN (min[1], attach[1].pos);
max[1] = MAX (max[1], attach[1].pos + attach[1].span);
}
request->lines[0].min = min[0];
request->lines[0].max = max[0];
request->lines[1].min = min[1];
request->lines[1].max = max[1];
}
/* Sets line sizes to 0 and marks lines as expand
* if they have a non-spanning expanding child.
*/
static void
clutter_grid_request_init (ClutterGridRequest *request,
ClutterOrientation orientation)
{
ClutterGridLayoutPrivate *priv = request->grid->priv;
ClutterGridChild *grid_child;
ClutterGridAttach *attach;
ClutterGridLines *lines;
ClutterActorIter iter;
ClutterActor *child;
gint i;
lines = &request->lines[orientation];
for (i = 0; i < lines->max - lines->min; i++)
{
lines->lines[i].minimum = 0;
lines->lines[i].natural = 0;
lines->lines[i].expand = FALSE;
}
clutter_actor_iter_init (&iter, CLUTTER_ACTOR (priv->container));
while (clutter_actor_iter_next (&iter, &child))
{
grid_child = GET_GRID_CHILD (request->grid, child);
attach = &grid_child->attach[orientation];
if (attach->span == 1 && clutter_actor_needs_expand (child, orientation))
lines->lines[attach->pos - lines->min].expand = TRUE;
}
}
/* Sums allocations for lines spanned by child and their spacing.
*/
static gfloat
compute_allocation_for_child (ClutterGridRequest *request,
ClutterActor *child,
ClutterOrientation orientation)
{
ClutterGridLayoutPrivate *priv = request->grid->priv;
ClutterGridChild *grid_child;
ClutterGridLineData *linedata;
ClutterGridLines *lines;
ClutterGridLine *line;
ClutterGridAttach *attach;
gfloat size;
gint i;
grid_child = GET_GRID_CHILD (request->grid, child);
linedata = &priv->linedata[orientation];
lines = &request->lines[orientation];
attach = &grid_child->attach[orientation];
size = (attach->span - 1) * linedata->spacing;
for (i = 0; i < attach->span; i++)
{
line = &lines->lines[attach->pos - lines->min + i];
size += line->allocation;
}
return size;
}
static void
compute_request_for_child (ClutterGridRequest *request,
ClutterActor *child,
ClutterOrientation orientation,
gboolean contextual,
gfloat *minimum,
gfloat *natural)
{
if (contextual)
{
gfloat size;
size = compute_allocation_for_child (request, child, 1 - orientation);
if (orientation == CLUTTER_ORIENTATION_HORIZONTAL)
clutter_actor_get_preferred_width (child, size, minimum, natural);
else
clutter_actor_get_preferred_height (child, size, minimum, natural);
}
else
{
if (orientation == CLUTTER_ORIENTATION_VERTICAL)
clutter_actor_get_preferred_width (child, -1, minimum, natural);
else
clutter_actor_get_preferred_height (child, -1, minimum, natural);
}
}
/* Sets requisition to max. of non-spanning children.
* If contextual is TRUE, requires allocations of
* lines in the opposite orientation to be set.
*/
static void
clutter_grid_request_non_spanning (ClutterGridRequest *request,
ClutterOrientation orientation,
gboolean contextual)
{
ClutterGridLayoutPrivate *priv = request->grid->priv;
ClutterGridChild *grid_child;
ClutterGridAttach *attach;
ClutterGridLines *lines;
ClutterGridLine *line;
ClutterActorIter iter;
ClutterActor *child;
gfloat minimum;
gfloat natural;
lines = &request->lines[orientation];
clutter_actor_iter_init (&iter, CLUTTER_ACTOR (priv->container));
while (clutter_actor_iter_next (&iter, &child))
{
if (!CLUTTER_ACTOR_IS_VISIBLE (child))
continue;
grid_child = GET_GRID_CHILD (request->grid, child);
attach = &grid_child->attach[orientation];
if (attach->span != 1)
continue;
compute_request_for_child (request, child, orientation, contextual, &minimum, &natural);
line = &lines->lines[attach->pos - lines->min];
line->minimum = MAX (line->minimum, minimum);
line->natural = MAX (line->natural, natural);
}
}
/* Enforce homogeneous sizes.
*/
static void
clutter_grid_request_homogeneous (ClutterGridRequest *request,
ClutterOrientation orientation)
{
ClutterGridLayoutPrivate *priv = request->grid->priv;
ClutterGridLineData *linedata;
ClutterGridLines *lines;
gfloat minimum, natural;
gint i;
linedata = &priv->linedata[orientation];
lines = &request->lines[orientation];
if (!linedata->homogeneous)
return;
minimum = 0.0f;
natural = 0.0f;
for (i = 0; i < lines->max - lines->min; i++)
{
minimum = MAX (minimum, lines->lines[i].minimum);
natural = MAX (natural, lines->lines[i].natural);
}
for (i = 0; i < lines->max - lines->min; i++)
{
lines->lines[i].minimum = minimum;
lines->lines[i].natural = natural;
}
}
/* Deals with spanning children.
* Requires expand fields of lines to be set for
* non-spanning children.
*/
static void
clutter_grid_request_spanning (ClutterGridRequest *request,
ClutterOrientation orientation,
gboolean contextual)
{
ClutterGridLayoutPrivate *priv = request->grid->priv;
ClutterGridChild *grid_child;
ClutterActor *child;
ClutterActorIter iter;
ClutterGridAttach *attach;
ClutterGridLineData *linedata;
ClutterGridLines *lines;
ClutterGridLine *line;
gfloat minimum;
gfloat natural;
gint span_minimum;
gint span_natural;
gint span_expand;
gboolean force_expand;
gint extra;
gint expand;
gint line_extra;
gint i;
linedata = &priv->linedata[orientation];
lines = &request->lines[orientation];
clutter_actor_iter_init (&iter, CLUTTER_ACTOR (priv->container));
while (clutter_actor_iter_next (&iter, &child))
{
if (!CLUTTER_ACTOR_IS_VISIBLE (child))
continue;
grid_child = GET_GRID_CHILD (request->grid, child);
attach = &grid_child->attach[orientation];
if (attach->span == 1)
continue;
compute_request_for_child (request, child, orientation, contextual,
&minimum, &natural);
span_minimum = (attach->span - 1) * linedata->spacing;
span_natural = (attach->span - 1) * linedata->spacing;
span_expand = 0;
force_expand = FALSE;
for (i = 0; i < attach->span; i++)
{
line = &lines->lines[attach->pos - lines->min + i];
span_minimum += line->minimum;
span_natural += line->natural;
if (line->expand)
span_expand += 1;
}
if (span_expand == 0)
{
span_expand = attach->span;
force_expand = TRUE;
}
/* If we need to request more space for this child to fill
* its requisition, then divide up the needed space amongst the
* lines it spans, favoring expandable lines if any.
*
* When doing homogeneous allocation though, try to keep the
* line allocations even, since we're going to force them to
* be the same anyway, and we don't want to introduce unnecessary
* extra space.
*/
if (span_minimum < minimum)
{
if (linedata->homogeneous)
{
gint total, m;
total = minimum - (attach->span - 1) * linedata->spacing;
m = total / attach->span + (total % attach->span ? 1 : 0);
for (i = 0; i < attach->span; i++)
{
line = &lines->lines[attach->pos - lines->min + i];
line->minimum = MAX(line->minimum, m);
}
}
else
{
extra = minimum - span_minimum;
expand = span_expand;
for (i = 0; i < attach->span; i++)
{
line = &lines->lines[attach->pos - lines->min + i];
if (force_expand || line->expand)
{
line_extra = extra / expand;
line->minimum += line_extra;
extra -= line_extra;
expand -= 1;
}
}
}
}
if (span_natural < natural)
{
if (linedata->homogeneous)
{
gint total, n;
total = natural - (attach->span - 1) * linedata->spacing;
n = total / attach->span + (total % attach->span ? 1 : 0);
for (i = 0; i < attach->span; i++)
{
line = &lines->lines[attach->pos - lines->min + i];
line->natural = MAX(line->natural, n);
}
}
else
{
extra = natural - span_natural;
expand = span_expand;
for (i = 0; i < attach->span; i++)
{
line = &lines->lines[attach->pos - lines->min + i];
if (force_expand || line->expand)
{
line_extra = extra / expand;
line->natural += line_extra;
extra -= line_extra;
expand -= 1;
}
}
}
}
}
}
/* Marks empty and expanding lines and counts them.
*/
static void
clutter_grid_request_compute_expand (ClutterGridRequest *request,
ClutterOrientation orientation,
gint *nonempty_lines,
gint *expand_lines)
{
ClutterGridLayoutPrivate *priv = request->grid->priv;
ClutterGridChild *grid_child;
ClutterGridAttach *attach;
ClutterActorIter iter;
ClutterActor *child;
gint i;
ClutterGridLines *lines;
ClutterGridLine *line;
gboolean has_expand;
gint expand;
gint empty;
lines = &request->lines[orientation];
for (i = 0; i < lines->max - lines->min; i++)
{
lines->lines[i].need_expand = FALSE;
lines->lines[i].expand = FALSE;
lines->lines[i].empty = TRUE;
}
clutter_actor_iter_init (&iter, CLUTTER_ACTOR (priv->container));
while (clutter_actor_iter_next (&iter, &child))
{
if (!CLUTTER_ACTOR_IS_VISIBLE (child))
continue;
grid_child = GET_GRID_CHILD (request->grid, child);
attach = &grid_child->attach[orientation];
if (attach->span != 1)
continue;
line = &lines->lines[attach->pos - lines->min];
line->empty = FALSE;
if (clutter_actor_needs_expand (child, orientation))
line->expand = TRUE;
}
clutter_actor_iter_init (&iter, CLUTTER_ACTOR (priv->container));
while (clutter_actor_iter_next (&iter, &child))
{
if (!CLUTTER_ACTOR_IS_VISIBLE (child))
continue;
grid_child = GET_GRID_CHILD (request->grid, child);
attach = &grid_child->attach[orientation];
if (attach->span == 1)
continue;
has_expand = FALSE;
for (i = 0; i < attach->span; i++)
{
line = &lines->lines[attach->pos - lines->min + i];
line->empty = FALSE;
if (line->expand)
has_expand = TRUE;
}
if (!has_expand && clutter_actor_needs_expand (child, orientation))
{
for (i = 0; i < attach->span; i++)
{
line = &lines->lines[attach->pos - lines->min + i];
line->need_expand = TRUE;
}
}
}
empty = 0;
expand = 0;
for (i = 0; i < lines->max - lines->min; i++)
{
line = &lines->lines[i];
if (line->need_expand)
line->expand = TRUE;
if (line->empty)
empty += 1;
if (line->expand)
expand += 1;
}
if (nonempty_lines)
*nonempty_lines = lines->max - lines->min - empty;
if (expand_lines)
*expand_lines = expand;
}
/* Sums the minimum and natural fields of lines and their spacing.
*/
static void
clutter_grid_request_sum (ClutterGridRequest *request,
ClutterOrientation orientation,
gfloat *minimum,
gfloat *natural)
{
ClutterGridLayoutPrivate *priv = request->grid->priv;
ClutterGridLineData *linedata;
ClutterGridLines *lines;
gint i;
gfloat min, nat;
gint nonempty;
clutter_grid_request_compute_expand (request, orientation, &nonempty, NULL);
linedata = &priv->linedata[orientation];
lines = &request->lines[orientation];
min = 0;
nat = 0;
if (nonempty > 0)
{
min = (nonempty - 1) * linedata->spacing;
nat = (nonempty - 1) * linedata->spacing;
}
for (i = 0; i < lines->max - lines->min; i++)
{
min += lines->lines[i].minimum;
nat += lines->lines[i].natural;
}
if (minimum)
*minimum = min;
if (natural)
*natural = nat;
}
/* Computes minimum and natural fields of lines.
* When contextual is TRUE, requires allocation of
* lines in the opposite orientation to be set.
*/
static void
clutter_grid_request_run (ClutterGridRequest *request,
ClutterOrientation orientation,
gboolean contextual)
{
clutter_grid_request_init (request, orientation);
clutter_grid_request_non_spanning (request, orientation, contextual);
clutter_grid_request_homogeneous (request, orientation);
clutter_grid_request_spanning (request, orientation, contextual);
clutter_grid_request_homogeneous (request, orientation);
}
typedef struct _RequestedSize
{
gpointer data;
gfloat minimum_size;
gfloat natural_size;
} RequestedSize;
/* Pulled from gtksizerequest.c from Gtk+ */
static gint
compare_gap (gconstpointer p1,
gconstpointer p2,
gpointer data)
{
RequestedSize *sizes = data;
const guint *c1 = p1;
const guint *c2 = p2;
const gint d1 = MAX (sizes[*c1].natural_size -
sizes[*c1].minimum_size,
0);
const gint d2 = MAX (sizes[*c2].natural_size -
sizes[*c2].minimum_size,
0);
gint delta = (d2 - d1);
if (0 == delta)
delta = (*c2 - *c1);
return delta;
}
/*
* distribute_natural_allocation:
* @extra_space: Extra space to redistribute among children after subtracting
* minimum sizes and any child padding from the overall allocation
* @n_requested_sizes: Number of requests to fit into the allocation
* @sizes: An array of structs with a client pointer and a minimum/natural size
* in the orientation of the allocation.
*
* Distributes @extra_space to child @sizes by bringing smaller
* children up to natural size first.
*
* The remaining space will be added to the @minimum_size member of the
* RequestedSize struct. If all sizes reach their natural size then
* the remaining space is returned.
*
* Returns: The remainder of @extra_space after redistributing space
* to @sizes.
*
* Pulled from gtksizerequest.c from Gtk+
*/
static gint
distribute_natural_allocation (gint extra_space,
guint n_requested_sizes,
RequestedSize *sizes)
{
guint *spreading;
gint i;
g_return_val_if_fail (extra_space >= 0, 0);
spreading = g_newa (guint, n_requested_sizes);
for (i = 0; i < n_requested_sizes; i++)
spreading[i] = i;
/* Distribute the container's extra space c_gap. We want to assign
* this space such that the sum of extra space assigned to children
* (c^i_gap) is equal to c_cap. The case that there's not enough
* space for all children to take their natural size needs some
* attention. The goals we want to achieve are:
*
* a) Maximize number of children taking their natural size.
* b) The allocated size of children should be a continuous
* function of c_gap. That is, increasing the container size by
* one pixel should never make drastic changes in the distribution.
* c) If child i takes its natural size and child j doesn't,
* child j should have received at least as much gap as child i.
*
* The following code distributes the additional space by following
* these rules.
*/
/* Sort descending by gap and position. */
g_qsort_with_data (spreading,
n_requested_sizes, sizeof (guint),
compare_gap, sizes);
/* Distribute available space.
* This master piece of a loop was conceived by Behdad Esfahbod.
*/
for (i = n_requested_sizes - 1; extra_space > 0 && i >= 0; --i)
{
/* Divide remaining space by number of remaining children.
* Sort order and reducing remaining space by assigned space
* ensures that space is distributed equally.
*/
gint glue = (extra_space + i) / (i + 1);
gint gap = sizes[(spreading[i])].natural_size
- sizes[(spreading[i])].minimum_size;
gint extra = MIN (glue, gap);
sizes[spreading[i]].minimum_size += extra;
extra_space -= extra;
}
return extra_space;
}
/* Requires that the minimum and natural fields of lines
* have been set, computes the allocation field of lines
* by distributing total_size among lines.
*/
static void
clutter_grid_request_allocate (ClutterGridRequest *request,
ClutterOrientation orientation,
gfloat total_size)
{
ClutterGridLayoutPrivate *priv = request->grid->priv;
ClutterGridLineData *linedata;
ClutterGridLines *lines;
ClutterGridLine *line;
gint nonempty;
gint expand;
gint i, j;
RequestedSize *sizes;
gint extra;
gint rest;
gint size;
clutter_grid_request_compute_expand (request, orientation, &nonempty, &expand);
if (nonempty == 0)
return;
linedata = &priv->linedata[orientation];
lines = &request->lines[orientation];
size = total_size - (nonempty - 1) * linedata->spacing;
if (linedata->homogeneous)
{
extra = size / nonempty;
rest = size % nonempty;
for (i = 0; i < lines->max - lines->min; i++)
{
line = &lines->lines[i];
if (line->empty)
continue;
line->allocation = extra;
if (rest > 0)
{
line->allocation += 1;
rest -= 1;
}
}
}
else
{
sizes = g_newa (RequestedSize, nonempty);
j = 0;
for (i = 0; i < lines->max - lines->min; i++)
{
line = &lines->lines[i];
if (line->empty)
continue;
size -= line->minimum;
sizes[j].minimum_size = line->minimum;
sizes[j].natural_size = line->natural;
sizes[j].data = line;
j++;
}
size = distribute_natural_allocation (MAX (0, size), nonempty, sizes);
if (expand > 0)
{
extra = size / expand;
rest = size % expand;
}
else
{
extra = 0;
rest = 0;
}
j = 0;
for (i = 0; i < lines->max - lines->min; i++)
{
line = &lines->lines[i];
if (line->empty)
continue;
g_assert (line == sizes[j].data);
line->allocation = sizes[j].minimum_size;
if (line->expand)
{
line->allocation += extra;
if (rest > 0)
{
line->allocation += 1;
rest -= 1;
}
}
j++;
}
}
}
/* Computes the position fields from allocation and spacing.
*/
static void
clutter_grid_request_position (ClutterGridRequest *request,
ClutterOrientation orientation)
{
ClutterGridLayoutPrivate *priv = request->grid->priv;
ClutterGridLineData *linedata;
ClutterGridLines *lines;
ClutterGridLine *line;
gfloat position;
gint i;
linedata = &priv->linedata[orientation];
lines = &request->lines[orientation];
position = 0.f;
for (i = 0; i < lines->max - lines->min; i++)
{
line = &lines->lines[i];
if (!line->empty)
{
line->position = position;
position += line->allocation + linedata->spacing;
}
}
}
static void
clutter_grid_child_set_property (GObject *gobject,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
ClutterGridChild *grid_child = CLUTTER_GRID_CHILD (gobject);
ClutterLayoutManager *manager;
manager = clutter_layout_meta_get_manager (CLUTTER_LAYOUT_META (gobject));
switch (prop_id)
{
case PROP_CHILD_LEFT_ATTACH:
CHILD_LEFT (grid_child) = g_value_get_int (value);
clutter_layout_manager_layout_changed (manager);
break;
case PROP_CHILD_TOP_ATTACH:
CHILD_TOP (grid_child) = g_value_get_int (value);
clutter_layout_manager_layout_changed (manager);
break;
case PROP_CHILD_WIDTH:
CHILD_WIDTH (grid_child) = g_value_get_int (value);
clutter_layout_manager_layout_changed (manager);
break;
case PROP_CHILD_HEIGHT:
CHILD_HEIGHT (grid_child) = g_value_get_int (value);
clutter_layout_manager_layout_changed (manager);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
break;
}
}
static void
clutter_grid_child_get_property (GObject *gobject,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
ClutterGridChild *grid_child = CLUTTER_GRID_CHILD (gobject);
switch (prop_id)
{
case PROP_CHILD_LEFT_ATTACH:
g_value_set_int (value, CHILD_LEFT (grid_child));
break;
case PROP_CHILD_TOP_ATTACH:
g_value_set_int (value, CHILD_TOP (grid_child));
break;
case PROP_CHILD_WIDTH:
g_value_set_int (value, CHILD_WIDTH (grid_child));
break;
case PROP_CHILD_HEIGHT:
g_value_set_int (value, CHILD_HEIGHT (grid_child));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
break;
}
}
static void
clutter_grid_child_class_init (ClutterGridChildClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->set_property = clutter_grid_child_set_property;
gobject_class->get_property = clutter_grid_child_get_property;
child_props[PROP_CHILD_LEFT_ATTACH] =
g_param_spec_int ("left-attach",
P_("Left attachment"),
P_("The column number to attach the left side of the "
"child to"),
-G_MAXINT, G_MAXINT, 0,
G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);
child_props[PROP_CHILD_TOP_ATTACH] =
g_param_spec_int ("top-attach",
P_("Top attachment"),
P_("The row number to attach the top side of a child "
"widget to"),
-G_MAXINT, G_MAXINT, 0,
G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);
child_props[PROP_CHILD_WIDTH] =
g_param_spec_int ("width",
P_("Width"),
P_("The number of columns that a child spans"),
-G_MAXINT, G_MAXINT, 1,
G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);
child_props[PROP_CHILD_HEIGHT] =
g_param_spec_int ("height",
P_("Height"),
P_("The number of rows that a child spans"),
-G_MAXINT, G_MAXINT, 1,
G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);
g_object_class_install_properties (gobject_class, PROP_CHILD_LAST,
child_props);
}
static void
clutter_grid_child_init (ClutterGridChild *self)
{
CHILD_LEFT (self) = -1;
CHILD_TOP (self) = -1;
CHILD_WIDTH (self) = 1;
CHILD_HEIGHT (self) = 1;
}
static void
clutter_grid_layout_set_container (ClutterLayoutManager *self,
ClutterContainer *container)
{
ClutterGridLayoutPrivate *priv = CLUTTER_GRID_LAYOUT (self)->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_ORIENTATION_VERTICAL
? 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_grid_layout_parent_class);
parent_class->set_container (self, container);
}
static void
clutter_grid_layout_get_preferred_width (ClutterLayoutManager *self,
ClutterContainer *container,
gfloat for_height,
gfloat *min_width_p,
gfloat *nat_width_p)
{
ClutterGridLayoutPrivate *priv = CLUTTER_GRID_LAYOUT (self)->priv;
ClutterGridRequest request;
ClutterGridLines *lines;
if (min_width_p)
*min_width_p = 0.0f;
if (nat_width_p)
*nat_width_p = 0.0f;
request.grid = CLUTTER_GRID_LAYOUT (self);
clutter_grid_request_update_attach (&request);
clutter_grid_request_count_lines (&request);
lines = &request.lines[priv->orientation];
lines->lines = g_newa (ClutterGridLine, lines->max - lines->min);
memset (lines->lines, 0, (lines->max - lines->min) * sizeof (ClutterGridLine));
clutter_grid_request_run (&request, priv->orientation, FALSE);
clutter_grid_request_sum (&request, priv->orientation,
min_width_p, nat_width_p);
}
static void
clutter_grid_layout_get_preferred_height (ClutterLayoutManager *self,
ClutterContainer *container,
gfloat for_width,
gfloat *min_height_p,
gfloat *nat_height_p)
{
ClutterGridLayoutPrivate *priv = CLUTTER_GRID_LAYOUT (self)->priv;
ClutterGridRequest request;
ClutterGridLines *lines;
if (min_height_p)
*min_height_p = 0.0f;
if (nat_height_p)
*nat_height_p = 0.0f;
request.grid = CLUTTER_GRID_LAYOUT (self);
clutter_grid_request_update_attach (&request);
clutter_grid_request_count_lines (&request);
lines = &request.lines[priv->orientation];
lines->lines = g_newa (ClutterGridLine, lines->max - lines->min);
memset (lines->lines, 0, (lines->max - lines->min) * sizeof (ClutterGridLine));
clutter_grid_request_run (&request, priv->orientation, FALSE);
clutter_grid_request_sum (&request, priv->orientation,
min_height_p, nat_height_p);
}
static void
allocate_child (ClutterGridRequest *request,
ClutterOrientation orientation,
ClutterGridChild *child,
gfloat *position,
gfloat *size)
{
ClutterGridLayoutPrivate *priv = request->grid->priv;
ClutterGridLineData *linedata;
ClutterGridLines *lines;
ClutterGridLine *line;
ClutterGridAttach *attach;
gint i;
linedata = &priv->linedata[orientation];
lines = &request->lines[orientation];
attach = &child->attach[orientation];
*position = lines->lines[attach->pos - lines->min].position;
*size = (attach->span - 1) * linedata->spacing;
for (i = 0; i < attach->span; i++)
{
line = &lines->lines[attach->pos - lines->min + i];
*size += line->allocation;
}
}
#define GET_SIZE(allocation, orientation) \
(orientation == CLUTTER_ORIENTATION_HORIZONTAL \
? clutter_actor_box_get_width ((allocation)) \
: clutter_actor_box_get_height ((allocation)))
static void
clutter_grid_layout_allocate (ClutterLayoutManager *layout,
ClutterContainer *container,
const ClutterActorBox *allocation,
ClutterAllocationFlags flags)
{
ClutterGridLayout *self = CLUTTER_GRID_LAYOUT (layout);
ClutterGridLayoutPrivate *priv = self->priv;
ClutterGridRequest request;
ClutterGridLines *lines;
ClutterActorIter iter;
ClutterActor *child;
gboolean use_animations;
ClutterAnimationMode mode;
guint duration, delay;
request.grid = self;
clutter_grid_request_update_attach (&request);
clutter_grid_request_count_lines (&request);
lines = &request.lines[0];
lines->lines = g_newa (ClutterGridLine, lines->max - lines->min);
memset (lines->lines, 0, (lines->max - lines->min) * sizeof (ClutterGridLine));
lines = &request.lines[1];
lines->lines = g_newa (ClutterGridLine, lines->max - lines->min);
memset (lines->lines, 0, (lines->max - lines->min) * sizeof (ClutterGridLine));
clutter_grid_request_run (&request, 1 - priv->orientation, FALSE);
clutter_grid_request_allocate (&request, 1 - priv->orientation, GET_SIZE (allocation, 1 - priv->orientation));
clutter_grid_request_run (&request, priv->orientation, TRUE);
clutter_grid_request_allocate (&request, priv->orientation, GET_SIZE (allocation, priv->orientation));
clutter_grid_request_position (&request, 0);
clutter_grid_request_position (&request, 1);
use_animations = clutter_layout_manager_get_easing_state (layout,
&mode,
&duration,
&delay);
clutter_actor_iter_init (&iter, CLUTTER_ACTOR (container));
while (clutter_actor_iter_next (&iter, &child))
{
ClutterActorBox child_allocation;
gfloat x, y, width, height;
ClutterGridChild *grid_child;
if (!CLUTTER_ACTOR_IS_VISIBLE (child))
continue;
grid_child = GET_GRID_CHILD (self, child);
allocate_child (&request, CLUTTER_ORIENTATION_HORIZONTAL, grid_child,
&x, &width);
allocate_child (&request, CLUTTER_ORIENTATION_VERTICAL, grid_child,
&y, &height);
x += allocation->x1;
y += allocation->y1;
clutter_actor_box_set_origin (&child_allocation, x, y);
clutter_actor_box_set_size (&child_allocation, width, height);
CLUTTER_NOTE (LAYOUT, "Allocation for %s { %.2f, %.2f - %.2f x %.2f }",
_clutter_actor_get_debug_name (child),
x, y, width, height);
if (use_animations)
{
clutter_actor_save_easing_state (child);
clutter_actor_set_easing_mode (child, mode);
clutter_actor_set_easing_duration (child, duration);
clutter_actor_set_easing_delay (child, delay);
}
clutter_actor_allocate (child, &child_allocation, flags);
if (use_animations)
clutter_actor_restore_easing_state (child);
}
}
static GType
clutter_grid_layout_get_child_meta_type (ClutterLayoutManager *self)
{
return CLUTTER_TYPE_GRID_CHILD;
}
static void
clutter_grid_layout_set_property (GObject *gobject,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
ClutterGridLayout *self = CLUTTER_GRID_LAYOUT (gobject);
switch (prop_id)
{
case PROP_ORIENTATION:
clutter_grid_layout_set_orientation (self, g_value_get_enum (value));
break;
case PROP_ROW_SPACING:
clutter_grid_layout_set_row_spacing (self, g_value_get_uint (value));
break;
case PROP_COLUMN_SPACING:
clutter_grid_layout_set_column_spacing (self, g_value_get_uint (value));
break;
case PROP_ROW_HOMOGENEOUS:
clutter_grid_layout_set_row_homogeneous (self,
g_value_get_boolean (value));
break;
case PROP_COLUMN_HOMOGENEOUS:
clutter_grid_layout_set_column_homogeneous (self,
g_value_get_boolean (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
break;
}
}
static void
clutter_grid_layout_get_property (GObject *gobject,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
ClutterGridLayoutPrivate *priv = CLUTTER_GRID_LAYOUT (gobject)->priv;
switch (prop_id)
{
case PROP_ORIENTATION:
g_value_set_enum (value, priv->orientation);
break;
case PROP_ROW_SPACING:
g_value_set_uint (value, COLUMNS (priv)->spacing);
break;
case PROP_COLUMN_SPACING:
g_value_set_uint (value, ROWS (priv)->spacing);
break;
case PROP_ROW_HOMOGENEOUS:
g_value_set_boolean (value, COLUMNS (priv)->homogeneous);
break;
case PROP_COLUMN_HOMOGENEOUS:
g_value_set_boolean (value, ROWS (priv)->homogeneous);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
break;
}
}
static void
clutter_grid_layout_class_init (ClutterGridLayoutClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
ClutterLayoutManagerClass *layout_class;
layout_class = CLUTTER_LAYOUT_MANAGER_CLASS (klass);
g_type_class_add_private (klass, sizeof (ClutterGridLayoutPrivate));
object_class->set_property = clutter_grid_layout_set_property;
object_class->get_property = clutter_grid_layout_get_property;
layout_class->set_container = clutter_grid_layout_set_container;
layout_class->get_preferred_width = clutter_grid_layout_get_preferred_width;
layout_class->get_preferred_height = clutter_grid_layout_get_preferred_height;
layout_class->allocate = clutter_grid_layout_allocate;
layout_class->get_child_meta_type = clutter_grid_layout_get_child_meta_type;
/**
* ClutterGridLayout:orientation:
*
* The orientation of the layout, either horizontal or vertical
*
* Since: 1.12
*/
obj_props[PROP_ORIENTATION] =
g_param_spec_enum ("orientation",
P_("Orientation"),
P_("The orientation of the layout"),
CLUTTER_TYPE_ORIENTATION,
CLUTTER_ORIENTATION_HORIZONTAL,
G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);
/**
* ClutterGridLayout:row-spacing:
*
* The amount of space in pixels between two consecutive rows
*
* Since: 1.12
*/
obj_props[PROP_ROW_SPACING] =
g_param_spec_uint ("row-spacing",
P_("Row spacing"),
P_("The amount of space between two consecutive rows"),
0, G_MAXUINT, 0,
G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);
/**
* ClutterGridLayout:column-spacing:
*
* The amount of space in pixels between two consecutive columns
*
* Since: 1.12
*/
obj_props[PROP_COLUMN_SPACING] =
g_param_spec_uint ("column-spacing",
P_("Column spacing"),
P_("The amount of space between two consecutive "
"columns"),
0, G_MAXUINT, 0,
G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);
/**
* ClutterGridLayout:row-homogeneous:
*
* Whether all rows of the layout should have the same height
*
* Since: 1.12
*/
obj_props[PROP_ROW_HOMOGENEOUS] =
g_param_spec_boolean ("row-homogeneous",
P_("Row Homogeneous"),
P_("If TRUE, the rows are all the same height"),
FALSE,
G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);
/**
* ClutterGridLayout:column-homogeneous:
*
* Whether all columns of the layout should have the same width
*
* Since: 1.12
*/
obj_props[PROP_COLUMN_HOMOGENEOUS] =
g_param_spec_boolean ("column-homogeneous",
P_("Column Homogeneous"),
P_("If TRUE, the columns are all the same width"),
FALSE,
G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);
g_object_class_install_properties (object_class, PROP_LAST, obj_props);
}
static void
clutter_grid_layout_init (ClutterGridLayout *self)
{
ClutterGridLayoutPrivate *priv;
self->priv = priv = CLUTTER_GRID_LAYOUT_GET_PRIVATE (self);
priv->orientation = CLUTTER_ORIENTATION_HORIZONTAL;
priv->linedata[0].spacing = 0;
priv->linedata[1].spacing = 0;
priv->linedata[0].homogeneous = FALSE;
priv->linedata[1].homogeneous = FALSE;
}
/**
* clutter_grid_layout_new:
*
* Creates a new #ClutterGridLayout
*
* Return value: the new #ClutterGridLayout
*/
ClutterLayoutManager *
clutter_grid_layout_new (void)
{
return g_object_new (CLUTTER_TYPE_GRID_LAYOUT, NULL);
}
/**
* clutter_grid_layout_attach:
* @layout: a #ClutterGridLayout
* @child: the #ClutterActor to add
* @left: the column number to attach the left side of @child to
* @top: the row number to attach the top side of @child to
* @width: the number of columns that @child will span
* @height: the number of rows that @child will span
*
* Adds a widget to the grid.
*
* The position of @child is determined by @left and @top. The
* number of 'cells' that @child will occupy is determined by
* @width and @height.
*
* Since: 1.12
*/
void
clutter_grid_layout_attach (ClutterGridLayout *layout,
ClutterActor *child,
gint left,
gint top,
gint width,
gint height)
{
ClutterGridLayoutPrivate *priv;
g_return_if_fail (CLUTTER_IS_GRID_LAYOUT (layout));
priv = layout->priv;
if (!priv->container)
return;
grid_attach (layout, child, left, top, width, height);
clutter_actor_add_child (CLUTTER_ACTOR (priv->container), child);
}
/**
* clutter_grid_layout_attach_next_to:
* @layout: a #ClutterGridLayout
* @child: the actor to add
* @sibling: (allow-none): the child of @layout that @child will be placed
* next to, or %NULL to place @child at the beginning or end
* @side: the side of @sibling that @child is positioned next to
* @width: the number of columns that @child will span
* @height: the number of rows that @child will span
*
* Adds a actor to the grid.
*
* The actor is placed next to @sibling, on the side determined by
* @side. When @sibling is %NULL, the actor is placed in row (for
* left or right placement) or column 0 (for top or bottom placement),
* at the end indicated by @side.
*
* Attaching widgets labeled [1], [2], [3] with @sibling == %NULL and
* @side == %CLUTTER_GRID_POSITION_LEFT yields a layout of [3][2][1].
*
* Since: 1.12
*/
void
clutter_grid_layout_attach_next_to (ClutterGridLayout *layout,
ClutterActor *child,
ClutterActor *sibling,
ClutterGridPosition side,
gint width,
gint height)
{
ClutterGridLayoutPrivate *priv;
g_return_if_fail (CLUTTER_IS_GRID_LAYOUT (layout));
g_return_if_fail (CLUTTER_IS_ACTOR (child));
g_return_if_fail (clutter_actor_get_parent (child) == NULL);
g_return_if_fail (sibling == NULL || CLUTTER_IS_ACTOR (sibling));
g_return_if_fail (width > 0);
g_return_if_fail (height > 0);
priv = layout->priv;
if (!priv->container)
return;
grid_attach_next_to (layout, child, sibling, side, width, height);
clutter_actor_add_child (CLUTTER_ACTOR (priv->container), child);
}
/**
* clutter_grid_layout_set_orientation:
* @layout: a #ClutterGridLayout
* @orientation: the orientation of the #ClutterGridLayout
*
* Sets the orientation of the @layout
*
* Since: 1.12
*/
void
clutter_grid_layout_set_orientation (ClutterGridLayout *layout,
ClutterOrientation orientation)
{
ClutterGridLayoutPrivate *priv;
g_return_if_fail (CLUTTER_IS_GRID_LAYOUT (layout));
priv = layout->priv;
if (priv->orientation != orientation)
{
priv->orientation = orientation;
clutter_layout_manager_layout_changed (CLUTTER_LAYOUT_MANAGER (layout));
g_object_notify_by_pspec (G_OBJECT (layout), obj_props[PROP_ORIENTATION]);
}
}
/**
* clutter_grid_layout_get_child_at:
* @layout: a #ClutterGridLayout
* @left: the left edge of the cell
* @top: the top edge of the cell
*
* Gets the child of @layout whose area covers the grid
* cell whose upper left corner is at @left, @top.
*
* Returns: (transfer none): the child at the given position, or %NULL
*
* Since: 1.12
*/
ClutterActor *
clutter_grid_layout_get_child_at (ClutterGridLayout *layout,
gint left,
gint top)
{
ClutterGridLayoutPrivate *priv;
ClutterGridChild *grid_child;
ClutterActorIter iter;
ClutterActor *child;
g_return_val_if_fail (CLUTTER_IS_GRID_LAYOUT (layout), NULL);
priv = layout->priv;
if (!priv->container)
return NULL;
clutter_actor_iter_init (&iter, CLUTTER_ACTOR (priv->container));
while (clutter_actor_iter_next (&iter, &child))
{
grid_child = GET_GRID_CHILD (layout, child);
if (CHILD_LEFT (grid_child) <= left &&
CHILD_LEFT (grid_child) + CHILD_WIDTH (grid_child) > left &&
CHILD_TOP (grid_child) <= top &&
CHILD_TOP (grid_child) + CHILD_HEIGHT (grid_child) > top)
return child;
}
return NULL;
}
/**
* clutter_grid_layout_insert_row:
* @layout: a #ClutterGridLayout
* @position: the position to insert the row at
*
* Inserts a row at the specified position.
*
* Children which are attached at or below this position
* are moved one row down. Children which span across this
* position are grown to span the new row.
*
* Since: 1.12
*/
void
clutter_grid_layout_insert_row (ClutterGridLayout *layout,
gint position)
{
ClutterGridLayoutPrivate *priv;
ClutterGridChild *grid_child;
ClutterActorIter iter;
ClutterActor *child;
gint top, height;
g_return_if_fail (CLUTTER_IS_GRID_LAYOUT (layout));
priv = layout->priv;
if (!priv->container)
return;
clutter_actor_iter_init (&iter, CLUTTER_ACTOR (priv->container));
while (clutter_actor_iter_next (&iter, &child))
{
grid_child = GET_GRID_CHILD (layout, child);
top = CHILD_TOP (grid_child);
height = CHILD_HEIGHT (grid_child);
if (top >= position)
{
CHILD_TOP (grid_child) = top + 1;
g_object_notify_by_pspec (G_OBJECT (grid_child),
child_props[PROP_CHILD_TOP_ATTACH]);
}
else if (top + height > position)
{
CHILD_HEIGHT (grid_child) = height + 1;
g_object_notify_by_pspec (G_OBJECT (grid_child),
child_props[PROP_CHILD_HEIGHT]);
}
}
clutter_layout_manager_layout_changed (CLUTTER_LAYOUT_MANAGER (layout));
}
/**
* clutter_grid_layout_insert_column:
* @layout: a #ClutterGridLayout
* @position: the position to insert the column at
*
* Inserts a column at the specified position.
*
* Children which are attached at or to the right of this position
* are moved one column to the right. Children which span across this
* position are grown to span the new column.
*
* Since: 1.12
*/
void
clutter_grid_layout_insert_column (ClutterGridLayout *layout,
gint position)
{
ClutterGridLayoutPrivate *priv;
ClutterGridChild *grid_child;
ClutterActorIter iter;
ClutterActor *child;
gint left, width;
g_return_if_fail (CLUTTER_IS_GRID_LAYOUT (layout));
priv = layout->priv;
if (!priv->container)
return;
clutter_actor_iter_init (&iter, CLUTTER_ACTOR (priv->container));
while (clutter_actor_iter_next (&iter, &child))
{
grid_child = GET_GRID_CHILD (layout, child);
left = CHILD_LEFT (grid_child);
width = CHILD_WIDTH (grid_child);
if (left >= position)
{
CHILD_LEFT (grid_child) = left + 1;
g_object_notify_by_pspec (G_OBJECT (grid_child),
child_props[PROP_CHILD_LEFT_ATTACH]);
}
else if (left + width > position)
{
CHILD_WIDTH (grid_child) = width + 1;
g_object_notify_by_pspec (G_OBJECT (grid_child),
child_props[PROP_CHILD_WIDTH]);
}
}
clutter_layout_manager_layout_changed (CLUTTER_LAYOUT_MANAGER (layout));
}
/**
* clutter_grid_layout_insert_next_to:
* @layout: a #ClutterGridLayout
* @sibling: the child of @layout that the new row or column will be
* placed next to
* @side: the side of @sibling that @child is positioned next to
*
* Inserts a row or column at the specified position.
*
* The new row or column is placed next to @sibling, on the side
* determined by @side. If @side is %CLUTTER_GRID_POSITION_LEFT or
* %CLUTTER_GRID_POSITION_BOTTOM, a row is inserted. If @side is
* %CLUTTER_GRID_POSITION_LEFT of %CLUTTER_GRID_POSITION_RIGHT,
* a column is inserted.
*
* Since: 1.12
*/
void
clutter_grid_layout_insert_next_to (ClutterGridLayout *layout,
ClutterActor *sibling,
ClutterGridPosition side)
{
ClutterGridChild *grid_child;
g_return_if_fail (CLUTTER_IS_GRID_LAYOUT (layout));
g_return_if_fail (CLUTTER_IS_ACTOR (sibling));
grid_child = GET_GRID_CHILD (layout, sibling);
switch (side)
{
case CLUTTER_GRID_POSITION_LEFT:
clutter_grid_layout_insert_column (layout, CHILD_LEFT (grid_child));
break;
case CLUTTER_GRID_POSITION_RIGHT:
clutter_grid_layout_insert_column (layout, CHILD_LEFT (grid_child) +
CHILD_WIDTH (grid_child));
break;
case CLUTTER_GRID_POSITION_TOP:
clutter_grid_layout_insert_row (layout, CHILD_TOP (grid_child));
break;
case CLUTTER_GRID_POSITION_BOTTOM:
clutter_grid_layout_insert_row (layout, CHILD_TOP (grid_child) +
CHILD_HEIGHT (grid_child));
break;
default:
g_assert_not_reached ();
}
}
/**
* clutter_grid_layout_get_orientation:
* @layout: a #ClutterGridLayout
*
* Retrieves the orientation of the @layout.
*
* Return value: the orientation of the layout
*
* Since: 1.12
*/
ClutterOrientation
clutter_grid_layout_get_orientation (ClutterGridLayout *layout)
{
g_return_val_if_fail (CLUTTER_IS_GRID_LAYOUT (layout),
CLUTTER_ORIENTATION_HORIZONTAL);
return layout->priv->orientation;
}
/**
* clutter_grid_layout_set_row_spacing:
* @layout: a #ClutterGridLayout
* @spacing: the spacing between rows of the layout, in pixels
*
* Sets the spacing between rows of @layout
*
* Since: 1.12
*/
void
clutter_grid_layout_set_row_spacing (ClutterGridLayout *layout,
guint spacing)
{
ClutterGridLayoutPrivate *priv;
g_return_if_fail (CLUTTER_IS_GRID_LAYOUT (layout));
priv = layout->priv;
if (COLUMNS (priv)->spacing != spacing)
{
COLUMNS (priv)->spacing = spacing;
clutter_layout_manager_layout_changed (CLUTTER_LAYOUT_MANAGER (layout));
g_object_notify_by_pspec (G_OBJECT (layout),
obj_props[PROP_ROW_SPACING]);
}
}
/**
* clutter_grid_layout_get_row_spacing:
* @layout: a #ClutterGridLayout
*
* Retrieves the spacing set using clutter_grid_layout_set_row_spacing()
*
* Return value: the spacing between rows of @layout
*
* Since: 1.12
*/
guint
clutter_grid_layout_get_row_spacing (ClutterGridLayout *layout)
{
ClutterGridLayoutPrivate *priv;
g_return_val_if_fail (CLUTTER_IS_GRID_LAYOUT (layout), 0);
priv = layout->priv;
return COLUMNS (priv)->spacing;
}
/**
* clutter_grid_layout_set_column_spacing:
* @layout: a #ClutterGridLayout
* @spacing: the spacing between columns of the layout, in pixels
*
* Sets the spacing between columns of @layout
*
* Since: 1.12
*/
void
clutter_grid_layout_set_column_spacing (ClutterGridLayout *layout,
guint spacing)
{
ClutterGridLayoutPrivate *priv;
g_return_if_fail (CLUTTER_IS_GRID_LAYOUT (layout));
priv = layout->priv;
if (ROWS (priv)->spacing != spacing)
{
ROWS (priv)->spacing = spacing;
clutter_layout_manager_layout_changed (CLUTTER_LAYOUT_MANAGER (layout));
g_object_notify_by_pspec (G_OBJECT (layout),
obj_props[PROP_COLUMN_SPACING]);
}
}
/**
* clutter_grid_layout_get_column_spacing:
* @layout: a #ClutterGridLayout
*
* Retrieves the spacing set using clutter_grid_layout_set_column_spacing()
*
* Return value: the spacing between coluns of @layout
*
* Since: 1.12
*/
guint
clutter_grid_layout_get_column_spacing (ClutterGridLayout *layout)
{
ClutterGridLayoutPrivate *priv;
g_return_val_if_fail (CLUTTER_IS_GRID_LAYOUT (layout), 0);
priv = layout->priv;
return ROWS (priv)->spacing;
}
/**
* clutter_grid_layout_set_column_homogeneous:
* @layout: a #ClutterGridLayout
* @homogeneous: %TRUE to make columns homogeneous
*
* Sets whether all columns of @layout will have the same width.
*
* Since: 1.12
*/
void
clutter_grid_layout_set_column_homogeneous (ClutterGridLayout *layout,
gboolean homogeneous)
{
ClutterGridLayoutPrivate *priv;
g_return_if_fail (CLUTTER_IS_GRID_LAYOUT (layout));
priv = layout->priv;
if (ROWS (priv)->homogeneous != homogeneous)
{
ROWS (priv)->homogeneous = homogeneous;
clutter_layout_manager_layout_changed (CLUTTER_LAYOUT_MANAGER (layout));
g_object_notify_by_pspec (G_OBJECT (layout),
obj_props[PROP_COLUMN_HOMOGENEOUS]);
}
}
/**
* clutter_grid_layout_get_column_homogeneous:
* @layout: a #ClutterGridLayout
*
* Returns whether all columns of @layout have the same width.
*
* Returns: whether all columns of @layout have the same width.
*/
gboolean
clutter_grid_layout_get_column_homogeneous (ClutterGridLayout *layout)
{
ClutterGridLayoutPrivate *priv;
g_return_val_if_fail (CLUTTER_IS_GRID_LAYOUT (layout), FALSE);
priv = layout->priv;
return ROWS (priv)->homogeneous;
}
/**
* clutter_grid_layout_set_row_homogeneous:
* @layout: a #ClutterGridLayout
* @homogeneous: %TRUE to make rows homogeneous
*
* Sets whether all rows of @layout will have the same height.
*
* Since: 1.12
*/
void
clutter_grid_layout_set_row_homogeneous (ClutterGridLayout *layout,
gboolean homogeneous)
{
ClutterGridLayoutPrivate *priv;
g_return_if_fail (CLUTTER_IS_GRID_LAYOUT (layout));
priv = layout->priv;
if (COLUMNS (priv)->homogeneous != homogeneous)
{
COLUMNS (priv)->homogeneous = homogeneous;
clutter_layout_manager_layout_changed (CLUTTER_LAYOUT_MANAGER (layout));
g_object_notify_by_pspec (G_OBJECT (layout),
obj_props[PROP_ROW_HOMOGENEOUS]);
}
}
/**
* clutter_grid_layout_get_row_homogeneous:
* @layout: a #ClutterGridLayout
*
* Returns whether all rows of @layout have the same height.
*
* Returns: whether all rows of @layout have the same height.
*
* Since: 1.12
*/
gboolean
clutter_grid_layout_get_row_homogeneous (ClutterGridLayout *layout)
{
ClutterGridLayoutPrivate *priv;
g_return_val_if_fail (CLUTTER_IS_GRID_LAYOUT (layout), FALSE);
priv = layout->priv;
return COLUMNS (priv)->homogeneous;
}