Sync allocation in ClutterBoxLayout with the one in GtkBox

https://bugzilla.gnome.org/show_bug.cgi?id=650487

Signed-off-by: Emmanuele Bassi <ebassi@linux.intel.com>
This commit is contained in:
Tomeu Vizoso 2011-05-18 16:10:18 +02:00 committed by Emmanuele Bassi
parent 9ecddeb1c3
commit e636a0bbce

View File

@ -631,66 +631,20 @@ static void
allocate_box_child (ClutterBoxLayout *self, allocate_box_child (ClutterBoxLayout *self,
ClutterContainer *container, ClutterContainer *container,
ClutterActor *child, ClutterActor *child,
gfloat *position, ClutterActorBox *child_box,
gfloat avail_width,
gfloat avail_height,
gfloat extra_space,
ClutterAllocationFlags flags) ClutterAllocationFlags flags)
{ {
ClutterBoxLayoutPrivate *priv = self->priv; ClutterBoxLayoutPrivate *priv = self->priv;
ClutterActorBox child_box; ClutterBoxChild *box_child;
ClutterBoxChild *box_child; ClutterLayoutMeta *meta;
ClutterLayoutMeta *meta; ClutterActorBox final_child_box;
gfloat child_nat;
if (!CLUTTER_ACTOR_IS_VISIBLE (child))
return;
meta = clutter_layout_manager_get_child_meta (CLUTTER_LAYOUT_MANAGER (self), meta = clutter_layout_manager_get_child_meta (CLUTTER_LAYOUT_MANAGER (self),
container, container,
child); child);
box_child = CLUTTER_BOX_CHILD (meta); box_child = CLUTTER_BOX_CHILD (meta);
if (priv->is_vertical) clutter_actor_allocate_align_fill (child, child_box,
{
clutter_actor_get_preferred_height (child, avail_width,
NULL, &child_nat);
child_nat = MIN (child_nat, avail_height);
child_box.y1 = floorf (*position + 0.5);
if (priv->is_homogeneous)
child_box.y2 = floorf (*position + extra_space + 0.5);
else if (box_child->expand)
child_box.y2 = floorf (*position + child_nat + extra_space + 0.5);
else
child_box.y2 = floorf (*position + child_nat + 0.5);
child_box.x1 = 0;
child_box.x2 = floorf (avail_width + 0.5);
}
else
{
clutter_actor_get_preferred_width (child, avail_height,
NULL, &child_nat);
child_nat = MIN (child_nat, avail_width);
child_box.x1 = floorf (*position + 0.5);
if (priv->is_homogeneous)
child_box.x2 = floorf (*position + extra_space + 0.5);
else if (box_child->expand)
child_box.x2 = floorf (*position + child_nat + extra_space + 0.5);
else
child_box.x2 = floorf (*position + child_nat + 0.5);
child_box.y1 = 0;
child_box.y2 = floorf (avail_height + 0.5);
}
clutter_actor_allocate_align_fill (child, &child_box,
get_box_alignment_factor (box_child->x_align), get_box_alignment_factor (box_child->x_align),
get_box_alignment_factor (box_child->y_align), get_box_alignment_factor (box_child->y_align),
box_child->x_fill, box_child->x_fill,
@ -700,7 +654,7 @@ allocate_box_child (ClutterBoxLayout *self,
/* retrieve the allocation computed and set by allocate_align_fill(); /* retrieve the allocation computed and set by allocate_align_fill();
* since we call this *after* allocate(), it's just a cheap copy * since we call this *after* allocate(), it's just a cheap copy
*/ */
clutter_actor_get_allocation_box (child, &child_box); clutter_actor_get_allocation_box (child, &final_child_box);
if (priv->use_animations && priv->is_animating) if (priv->use_animations && priv->is_animating)
{ {
@ -716,17 +670,17 @@ allocate_box_child (ClutterBoxLayout *self,
* been added to the container; we put it in the final state * been added to the container; we put it in the final state
* and store its allocation for later * and store its allocation for later
*/ */
box_child->last_allocation = child_box; box_child->last_allocation = final_child_box;
box_child->has_last_allocation = TRUE; box_child->has_last_allocation = TRUE;
goto do_allocate; goto do_allocate;
} }
start = &box_child->last_allocation; start = &box_child->last_allocation;
end = child_box; end = final_child_box;
/* interpolate between the initial and final values */ /* interpolate between the initial and final values */
clutter_actor_box_interpolate (start, &end, p, &child_box); clutter_actor_box_interpolate (start, &end, p, &final_child_box);
CLUTTER_NOTE (ANIMATION, CLUTTER_NOTE (ANIMATION,
"Animate { %.1f, %.1f, %.1f, %.1f }\t" "Animate { %.1f, %.1f, %.1f, %.1f }\t"
@ -735,27 +689,20 @@ allocate_box_child (ClutterBoxLayout *self,
start->x1, start->y1, start->x1, start->y1,
start->x2, start->y2, start->x2, start->y2,
p, p,
child_box.x1, child_box.y1, final_child_box.x1, final_child_box.y1,
child_box.x2, child_box.y2, final_child_box.x2, final_child_box.y2,
end.x1, end.y1, end.x1, end.y1,
end.x2, end.y2); end.x2, end.y2);
} }
else else
{ {
/* store the allocation for later animations */ /* store the allocation for later animations */
box_child->last_allocation = child_box; box_child->last_allocation = final_child_box;
box_child->has_last_allocation = TRUE; box_child->has_last_allocation = TRUE;
} }
do_allocate: do_allocate:
clutter_actor_allocate (child, &child_box, flags); clutter_actor_allocate (child, &final_child_box, flags);
if (priv->is_homogeneous)
*position += (priv->spacing + extra_space);
else if (box_child->expand)
*position += (child_nat + priv->spacing + extra_space);
else
*position += (child_nat + priv->spacing);
} }
static void static void
@ -796,6 +743,156 @@ clutter_box_layout_get_preferred_height (ClutterLayoutManager *layout,
g_list_free (children); g_list_free (children);
} }
static void
count_expand_children (ClutterLayoutManager *layout,
ClutterContainer *container,
gint *visible_children,
gint *expand_children)
{
GList *children;
ClutterActor *child;
*visible_children = *expand_children = 0;
for (children = clutter_container_get_children (container);
children;
children = children->next)
{
child = children->data;
if (CLUTTER_ACTOR_IS_VISIBLE (child))
{
ClutterLayoutMeta *meta;
*visible_children += 1;
meta = clutter_layout_manager_get_child_meta (layout,
container,
child);
if (CLUTTER_BOX_CHILD (meta)->expand)
*expand_children += 1;
}
}
g_list_free (children);
}
struct _ClutterRequestedSize
{
ClutterActor *actor;
gfloat minimum_size;
gfloat natural_size;
};
/* Pulled from gtksizerequest.c from Gtk+ */
static gint
compare_gap (gconstpointer p1,
gconstpointer p2,
gpointer data)
{
struct _ClutterRequestedSize *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
* GtkRequestedSize 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,
struct _ClutterRequestedSize *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;
}
/* Pulled from gtkbox.c from Gtk+ */
static void static void
clutter_box_layout_allocate (ClutterLayoutManager *layout, clutter_box_layout_allocate (ClutterLayoutManager *layout,
ClutterContainer *container, ClutterContainer *container,
@ -803,82 +900,112 @@ clutter_box_layout_allocate (ClutterLayoutManager *layout,
ClutterAllocationFlags flags) ClutterAllocationFlags flags)
{ {
ClutterBoxLayoutPrivate *priv = CLUTTER_BOX_LAYOUT (layout)->priv; ClutterBoxLayoutPrivate *priv = CLUTTER_BOX_LAYOUT (layout)->priv;
gfloat avail_width, avail_height, pref_width, pref_height; ClutterActor *child;
gint n_expand_children, n_children, extra_space; GList *children;
GList *children, *l; gint nvis_children;
gfloat position; gint nexpand_children;
gboolean is_rtl; gboolean is_rtl;
children = clutter_container_get_children (container); ClutterActorBox child_allocation;
if (children == NULL) struct _ClutterRequestedSize *sizes;
gint size;
gint extra;
gint n_extra_widgets = 0; /* Number of widgets that receive 1 extra px */
gint x = 0, y = 0, i;
gint child_size;
count_expand_children (layout, container, &nvis_children, &nexpand_children);
/* If there is no visible child, simply return. */
if (nvis_children <= 0)
return; return;
clutter_actor_box_get_size (box, &avail_width, &avail_height); sizes = g_newa (struct _ClutterRequestedSize, nvis_children);
if (priv->is_vertical) if (priv->is_vertical)
{ size = box->y2 - box->y1 - (nvis_children - 1) * priv->spacing;
get_preferred_height (CLUTTER_BOX_LAYOUT (layout),
container,
children, avail_width,
NULL,
&pref_height);
pref_width = avail_width;
}
else else
size = box->x2 - box->x1 - (nvis_children - 1) * priv->spacing;
/* Retrieve desired size for visible children. */
for (i = 0, children = clutter_container_get_children (container);
children;
children = children->next)
{ {
get_preferred_width (CLUTTER_BOX_LAYOUT (layout), child = children->data;
container,
children, avail_height,
NULL,
&pref_width);
pref_height = avail_height; if (!CLUTTER_ACTOR_IS_VISIBLE (child))
} continue;
/* count the number of children with expand set to TRUE */ if (priv->is_vertical)
n_children = n_expand_children = 0; clutter_actor_get_preferred_height (child,
for (l = children; l; l = l->next) box->x2 - box->x1,
{ &sizes[i].minimum_size,
ClutterLayoutMeta *meta; &sizes[i].natural_size);
else
meta = clutter_layout_manager_get_child_meta (layout, clutter_actor_get_preferred_width (child,
container, box->y2 - box->y1,
l->data); &sizes[i].minimum_size,
&sizes[i].natural_size);
if (CLUTTER_BOX_CHILD (meta)->expand)
n_expand_children++;
/* Assert the api is working properly */
n_children++; if (sizes[i].minimum_size < 0)
g_error ("GtkBox child %s minimum %s: %f < 0 for %s %f",
clutter_actor_get_name (child),
priv->is_vertical ? "height" : "width",
sizes[i].minimum_size,
priv->is_vertical ? "width" : "height",
priv->is_vertical ? box->x2 - box->x1 : box->y2 - box->y1);
if (sizes[i].natural_size < sizes[i].minimum_size)
g_error ("GtkBox child %s natural %s: %f < minimum %f for %s %f",
clutter_actor_get_name (child),
priv->is_vertical ? "height" : "width",
sizes[i].natural_size,
sizes[i].minimum_size,
priv->is_vertical ? "width" : "height",
priv->is_vertical ? box->x2 - box->x1 : box->y2 - box->y1);
size -= sizes[i].minimum_size;
sizes[i].actor = child;
i++;
} }
g_list_free (children);
if (priv->is_homogeneous) if (priv->is_homogeneous)
{ {
/* If were homogenous we still need to run the above loop to get the
* minimum sizes for children that are not going to fill
*/
if (priv->is_vertical) if (priv->is_vertical)
extra_space = (avail_height - (n_children - 1) * priv->spacing) size = box->y2 - box->y1 - (nvis_children - 1) * priv->spacing;
/ n_children;
else else
extra_space = (avail_width - (n_children - 1) * priv->spacing) size = box->x2 - box->x1 - (nvis_children - 1) * priv->spacing;
/ n_children;
} extra = size / nvis_children;
else if (n_expand_children == 0) n_extra_widgets = size % nvis_children;
{
extra_space = 0;
} }
else else
{ {
if (priv->is_vertical) /* Bring children up to size first */
extra_space = (avail_height - pref_height) / n_expand_children; size = distribute_natural_allocation (MAX (0, size), nvis_children, sizes);
/* Calculate space which hasn't distributed yet,
* and is available for expanding children.
*/
if (nexpand_children > 0)
{
extra = size / nexpand_children;
n_extra_widgets = size % nexpand_children;
}
else else
extra_space = (avail_width - pref_width) / n_expand_children; extra = 0;
/* don't shrink anything */
if (extra_space < 0)
extra_space = 0;
} }
position = 0;
if (!priv->is_vertical) if (!priv->is_vertical)
{ {
ClutterTextDirection text_dir; ClutterTextDirection text_dir;
@ -889,37 +1016,138 @@ clutter_box_layout_allocate (ClutterLayoutManager *layout,
else else
is_rtl = FALSE; is_rtl = FALSE;
if (is_rtl) /* Allocate child positions. */
if (priv->is_vertical)
{ {
for (l = (priv->is_pack_start) ? children : g_list_last (children); child_allocation.x1 = 0.0;
l != NULL; child_allocation.x2 = MAX (1.0, box->x2 - box->x1);
l = (priv->is_pack_start) ? l->next : l->prev) if (priv->is_pack_start)
{ y = 0.0;
ClutterActor *child = l->data; else
y = box->y2 - box->y1;
allocate_box_child (CLUTTER_BOX_LAYOUT (layout), container, child,
&position,
avail_width,
avail_height,
extra_space, flags);
}
} }
else else
{ {
for (l = (priv->is_pack_start) ? g_list_last (children) : children; child_allocation.y1 = 0.0;
l != NULL; child_allocation.y2 = MAX (1.0, box->y2 - box->y1);
l = (priv->is_pack_start) ? l->prev : l->next) if (priv->is_pack_start)
{ x = 0.0;
ClutterActor *child = l->data; else
x = 0.0 + box->x2 - box->x1;
allocate_box_child (CLUTTER_BOX_LAYOUT (layout), container, child,
&position,
avail_width,
avail_height,
extra_space, flags);
}
} }
children = clutter_container_get_children (container);
for (i = g_list_length (children) - 1, children = g_list_last (children);
children;
children = children->prev, i--)
{
ClutterLayoutMeta *meta;
ClutterBoxChild *box_child;
child = children->data;
meta = clutter_layout_manager_get_child_meta (layout,
container,
child);
box_child = CLUTTER_BOX_CHILD (meta);
/* If widget is not visible, skip it. */
if (!CLUTTER_ACTOR_IS_VISIBLE (child))
continue;
/* Assign the child's size. */
if (priv->is_homogeneous)
{
child_size = extra;
if (n_extra_widgets > 0)
{
child_size++;
n_extra_widgets--;
}
}
else
{
child_size = sizes[i].minimum_size;
if (box_child->expand)
{
child_size += extra;
if (n_extra_widgets > 0)
{
child_size++;
n_extra_widgets--;
}
}
}
/* Assign the child's position. */
if (priv->is_vertical)
{
if (box_child->y_fill)
{
child_allocation.y1 = y;
child_allocation.y2 = child_allocation.y1 + MAX (1.0, child_size);
}
else
{
child_allocation.y1 = y + (child_size - sizes[i].minimum_size) / 2;
child_allocation.y2 = child_allocation.y1 + sizes[i].minimum_size;
}
if (priv->is_pack_start)
{
y += child_size + priv->spacing;
}
else
{
y -= child_size + priv->spacing;
child_allocation.y1 -= child_size;
child_allocation.y2 -= child_size;
}
}
else /* !priv->is_vertical */
{
if (box_child->x_fill)
{
child_allocation.x1 = x;
child_allocation.x2 = child_allocation.x1 + MAX (1, child_size);
}
else
{
child_allocation.x1 = x + (child_size - sizes[i].minimum_size) / 2;
child_allocation.x2 = child_allocation.x1 + sizes[i].minimum_size;
}
if (priv->is_pack_start)
{
x += child_size + priv->spacing;
}
else
{
x -= child_size + priv->spacing;
child_allocation.x1 -= child_size;
child_allocation.x2 -= child_size;
}
if (is_rtl)
{
gfloat width = child_allocation.x2 - child_allocation.x1;
child_allocation.x1 = box->x2 - box->x1 - child_allocation.x1 - (child_allocation.x2 - child_allocation.x1);
child_allocation.x2 = child_allocation.x1 + width;
}
}
allocate_box_child (CLUTTER_BOX_LAYOUT (layout),
container,
child,
&child_allocation,
flags);
}
g_list_free (children); g_list_free (children);
} }