forked from AuroraMiddleware/gtk
Compute the collective heights for the width of a horizontal box.
Introduce an algorithm to allocate children some virtual widths based on their base widths returned by ->get_desired_width(), then return the collective desired heights for each or thier virtually allocated width. This will only work in the horizontal orientation.
This commit is contained in:
parent
33039c1452
commit
35cc52f418
380
gtk/gtkbox.c
380
gtk/gtkbox.c
@ -110,7 +110,6 @@ static GType gtk_box_child_type (GtkContainer *container);
|
||||
|
||||
|
||||
static void gtk_box_extended_layout_init (GtkExtendedLayoutIface *iface);
|
||||
static gboolean gtk_box_is_height_for_width (GtkExtendedLayout *layout);
|
||||
static void gtk_box_get_desired_width (GtkExtendedLayout *layout,
|
||||
gint *minimum_size,
|
||||
gint *natural_size);
|
||||
@ -285,6 +284,27 @@ gtk_box_get_property (GObject *object,
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
count_expand_children (GtkBox *box, gint *visible_children, gint *expand_children)
|
||||
{
|
||||
GList *children;
|
||||
GtkBoxChild *child;
|
||||
|
||||
*visible_children = *expand_children = 0;
|
||||
|
||||
for (children = box->children; children; children = children->next)
|
||||
{
|
||||
child = children->data;
|
||||
|
||||
if (gtk_widget_get_visible (child->widget))
|
||||
{
|
||||
*visible_children += 1;
|
||||
if (child->expand)
|
||||
*expand_children += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static gint
|
||||
gtk_box_compare_gap (gconstpointer p1,
|
||||
gconstpointer p2,
|
||||
@ -309,7 +329,6 @@ gtk_box_compare_gap (gconstpointer p1,
|
||||
return delta;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
gtk_box_size_allocate (GtkWidget *widget,
|
||||
GtkAllocation *allocation)
|
||||
@ -323,20 +342,7 @@ gtk_box_size_allocate (GtkWidget *widget,
|
||||
|
||||
widget->allocation = *allocation;
|
||||
|
||||
nvis_children = 0;
|
||||
nexpand_children = 0;
|
||||
|
||||
for (children = box->children; children; children = children->next)
|
||||
{
|
||||
child = children->data;
|
||||
|
||||
if (gtk_widget_get_visible (child->widget))
|
||||
{
|
||||
nvis_children += 1;
|
||||
if (child->expand)
|
||||
nexpand_children += 1;
|
||||
}
|
||||
}
|
||||
count_expand_children (box, &nvis_children, &nexpand_children);
|
||||
|
||||
if (nvis_children > 0)
|
||||
{
|
||||
@ -765,21 +771,12 @@ gtk_box_extended_layout_init (GtkExtendedLayoutIface *iface)
|
||||
{
|
||||
parent_extended_layout_iface = g_type_interface_peek_parent (iface);
|
||||
|
||||
iface->is_height_for_width = gtk_box_is_height_for_width;
|
||||
iface->get_desired_width = gtk_box_get_desired_width;
|
||||
iface->get_desired_height = gtk_box_get_desired_height;
|
||||
iface->get_height_for_width = gtk_box_get_height_for_width;
|
||||
iface->get_width_for_height = gtk_box_get_width_for_height;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_box_is_height_for_width (GtkExtendedLayout *layout)
|
||||
{
|
||||
GtkBoxPrivate *private = GTK_BOX_GET_PRIVATE (layout);
|
||||
|
||||
return (private->orientation == GTK_ORIENTATION_HORIZONTAL);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_box_get_desired_size (GtkExtendedLayout *layout,
|
||||
GtkOrientation orientation,
|
||||
@ -882,148 +879,208 @@ gtk_box_get_desired_height (GtkExtendedLayout *layout,
|
||||
gtk_box_get_desired_size (layout, GTK_ORIENTATION_VERTICAL, minimum_size, natural_size);
|
||||
}
|
||||
|
||||
/**
|
||||
* size_fits_for_dimension:
|
||||
* @box: a GtkBox
|
||||
* @avail_size: the allocated size in @box's opposing orientation
|
||||
* @check_size: the size in @box's orientation to check
|
||||
* @check_natural: whether to check natural sizes or minimum sizes.
|
||||
*
|
||||
* This checks if the required size of @box and its children fit into @avail_size
|
||||
* in @box's opposing orientation if @box were given @check_size as an allocation
|
||||
* in @box's orientation.
|
||||
*
|
||||
* In context: A GtkVBox will check if it fits into the available allocated height
|
||||
* if it were given the @check_size in width.
|
||||
*
|
||||
*/
|
||||
static gboolean
|
||||
size_fits_for_dimension (GtkBox *box,
|
||||
gint avail_size,
|
||||
gint check_size,
|
||||
gboolean check_natural)
|
||||
{
|
||||
GtkBoxPrivate *private = GTK_BOX_GET_PRIVATE (box);
|
||||
GList *children;
|
||||
gint nvis_children = 0;
|
||||
gint required_size = 0, child_size;
|
||||
gint largest_child = 0;
|
||||
|
||||
avail_size -= GTK_CONTAINER (box)->border_width * 2;
|
||||
|
||||
for (children = box->children; children != NULL;
|
||||
children = children->next, nvis_children++)
|
||||
{
|
||||
GtkBoxChild *child = children->data;
|
||||
|
||||
if (gtk_widget_get_visible (child->widget))
|
||||
{
|
||||
|
||||
if (private->orientation == GTK_ORIENTATION_HORIZONTAL)
|
||||
{
|
||||
if (check_natural)
|
||||
gtk_extended_layout_get_width_for_height (GTK_EXTENDED_LAYOUT (child->widget),
|
||||
avail_size, NULL, &child_size);
|
||||
else
|
||||
gtk_extended_layout_get_width_for_height (GTK_EXTENDED_LAYOUT (child->widget),
|
||||
avail_size, &child_size, NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (check_natural)
|
||||
gtk_extended_layout_get_height_for_width (GTK_EXTENDED_LAYOUT (child->widget),
|
||||
avail_size, NULL, &child_size);
|
||||
else
|
||||
gtk_extended_layout_get_height_for_width (GTK_EXTENDED_LAYOUT (child->widget),
|
||||
avail_size, &child_size, NULL);
|
||||
}
|
||||
|
||||
child_size += child->padding * 2;
|
||||
|
||||
if (child_size > largest_child)
|
||||
largest_child = child_size;
|
||||
|
||||
required_size += child_size;
|
||||
}
|
||||
}
|
||||
|
||||
if (nvis_children > 0)
|
||||
{
|
||||
if (box->homogeneous)
|
||||
required_size = largest_child * nvis_children;
|
||||
|
||||
required_size += (nvis_children - 1) * box->spacing;
|
||||
}
|
||||
|
||||
required_size += GTK_CONTAINER (box)->border_width * 2;
|
||||
|
||||
return required_size <= check_size;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
gtk_box_bisect_for_size_in_opposing_orientation (GtkBox *box,
|
||||
gboolean check_natural,
|
||||
gint avail_size,
|
||||
gint floor,
|
||||
gint ceiling,
|
||||
gint *size)
|
||||
{
|
||||
if (ceiling - floor <= 1)
|
||||
*size = ceiling;
|
||||
else
|
||||
{
|
||||
gint check_size = floor + (ceiling - floor) / 2;
|
||||
|
||||
if (size_fits_for_dimension (box, avail_size, check_size, check_natural))
|
||||
|
||||
/* If check_size is large enough for box to fit into avail_size, we go on
|
||||
* to check between the given floor and check_size as the new ceiling
|
||||
*/
|
||||
|
||||
gtk_box_bisect_for_size_in_opposing_orientation (box, check_natural, avail_size,
|
||||
floor, check_size, size);
|
||||
else
|
||||
gtk_box_bisect_for_size_in_opposing_orientation (box, check_natural, avail_size,
|
||||
check_size, ceiling, size);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_box_compute_size_for_opposing_orientation (GtkBox *box,
|
||||
gint avail_size,
|
||||
gint *minimum_size,
|
||||
gint *natural_size)
|
||||
{
|
||||
gint minimum, natural;
|
||||
gint min_ceiling, nat_ceiling;
|
||||
gint step = 200;
|
||||
GtkBoxPrivate *private = GTK_BOX_GET_PRIVATE (box);
|
||||
GtkBoxChild *child;
|
||||
GList *children;
|
||||
gint nvis_children;
|
||||
gint nexpand_children;
|
||||
gint computed_minimum = 0, computed_natural = 0;
|
||||
gint border_width = GTK_CONTAINER (box)->border_width;
|
||||
|
||||
/* First a large gap to search inside of
|
||||
*/
|
||||
for (min_ceiling = (step + 1); !size_fits_for_dimension (box, avail_size, min_ceiling, FALSE); min_ceiling += step);
|
||||
count_expand_children (box, &nvis_children, &nexpand_children);
|
||||
|
||||
/* This will find the minimum sizes by halfing the guesses until they are found
|
||||
*/
|
||||
gtk_box_bisect_for_size_in_opposing_orientation (box, FALSE, avail_size,
|
||||
min_ceiling - step,
|
||||
min_ceiling,
|
||||
&minimum);
|
||||
if (nvis_children > 0)
|
||||
{
|
||||
GtkBoxSpreading *spreading = g_newa (GtkBoxSpreading, nvis_children);
|
||||
GtkBoxDesiredSizes *sizes = g_newa (GtkBoxDesiredSizes, nvis_children);
|
||||
GtkPackType packing;
|
||||
gint size;
|
||||
gint extra, i;
|
||||
gint child_size, child_minimum, child_natural;
|
||||
|
||||
/* Basing the natural size on the found minimum, do the same operation for the natural size */
|
||||
for (nat_ceiling = minimum + step; !size_fits_for_dimension (box, avail_size, nat_ceiling, TRUE); nat_ceiling += step);
|
||||
gtk_box_bisect_for_size_in_opposing_orientation (box, TRUE, avail_size,
|
||||
nat_ceiling - step,
|
||||
nat_ceiling,
|
||||
&natural);
|
||||
size = avail_size - border_width * 2 - (nvis_children - 1) * box->spacing;
|
||||
|
||||
/* Retrieve desired size for visible children */
|
||||
for (i = 0, children = box->children; children; children = children->next)
|
||||
{
|
||||
child = children->data;
|
||||
|
||||
if (gtk_widget_get_visible (child->widget))
|
||||
{
|
||||
if (private->orientation == GTK_ORIENTATION_HORIZONTAL)
|
||||
gtk_extended_layout_get_desired_width (GTK_EXTENDED_LAYOUT (child->widget),
|
||||
&sizes[i].minimum_size,
|
||||
&sizes[i].natural_size);
|
||||
else
|
||||
gtk_extended_layout_get_desired_height (GTK_EXTENDED_LAYOUT (child->widget),
|
||||
&sizes[i].minimum_size,
|
||||
&sizes[i].natural_size);
|
||||
|
||||
/* Assert the api is working properly */
|
||||
if (sizes[i].minimum_size < 0)
|
||||
g_error ("GtkBox child %s minimum %s: %d < 0",
|
||||
gtk_widget_get_name (GTK_WIDGET (child->widget)),
|
||||
(private->orientation == GTK_ORIENTATION_HORIZONTAL) ? "width" : "height",
|
||||
sizes[i].minimum_size);
|
||||
|
||||
if (sizes[i].natural_size < sizes[i].minimum_size)
|
||||
g_error ("GtkBox child %s natural %s: %d < minimum %d",
|
||||
gtk_widget_get_name (GTK_WIDGET (child->widget)),
|
||||
(private->orientation == GTK_ORIENTATION_HORIZONTAL) ? "width" : "height",
|
||||
sizes[i].natural_size,
|
||||
sizes[i].minimum_size);
|
||||
|
||||
size -= sizes[i].minimum_size;
|
||||
size -= child->padding * 2;
|
||||
|
||||
spreading[i].index = i;
|
||||
spreading[i].child = child;
|
||||
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (box->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
|
||||
*/
|
||||
size = avail_size - border_width * 2 - (nvis_children - 1) * box->spacing;
|
||||
extra = size / nvis_children;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
/* 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
|
||||
* this rules.
|
||||
*/
|
||||
|
||||
/* Sort descending by gap and position. */
|
||||
|
||||
g_qsort_with_data (spreading,
|
||||
nvis_children, sizeof (GtkBoxSpreading),
|
||||
gtk_box_compare_gap, sizes);
|
||||
|
||||
/* Distribute available space.
|
||||
* This master piece of a loop was conceived by Behdad Esfahbod.
|
||||
*/
|
||||
for (i = nvis_children - 1; 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 = (size + i) / (i + 1);
|
||||
gint gap = sizes[spreading[i].index].natural_size
|
||||
- sizes[spreading[i].index].minimum_size;
|
||||
|
||||
extra = MIN (glue, gap);
|
||||
sizes[spreading[i].index].minimum_size += extra;
|
||||
|
||||
size -= extra;
|
||||
}
|
||||
|
||||
/* Calculate space which hasn't distributed yet,
|
||||
* and is available for expanding children.
|
||||
*/
|
||||
if (nexpand_children > 0)
|
||||
extra = size / nexpand_children;
|
||||
else
|
||||
extra = 0;
|
||||
}
|
||||
|
||||
/* Allocate child positions. */
|
||||
for (packing = GTK_PACK_START; packing <= GTK_PACK_END; ++packing)
|
||||
{
|
||||
for (i = 0, children = box->children; children; children = children->next)
|
||||
{
|
||||
child = children->data;
|
||||
|
||||
if (gtk_widget_get_visible (child->widget))
|
||||
{
|
||||
if (child->pack == packing)
|
||||
{
|
||||
/* Assign the child's size. */
|
||||
if (box->homogeneous)
|
||||
{
|
||||
if (nvis_children == 1)
|
||||
child_size = size;
|
||||
else
|
||||
child_size = extra;
|
||||
|
||||
nvis_children -= 1;
|
||||
size -= extra;
|
||||
}
|
||||
else
|
||||
{
|
||||
child_size = sizes[i].minimum_size + child->padding * 2;
|
||||
|
||||
if (child->expand)
|
||||
{
|
||||
if (nexpand_children == 1)
|
||||
child_size += size;
|
||||
else
|
||||
child_size += extra;
|
||||
|
||||
nexpand_children -= 1;
|
||||
size -= extra;
|
||||
}
|
||||
}
|
||||
|
||||
if (child->fill)
|
||||
{
|
||||
child_size = MAX (1, child_size - child->padding * 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
child_size = sizes[i].minimum_size;
|
||||
}
|
||||
|
||||
|
||||
/* Assign the child's position. */
|
||||
if (private->orientation == GTK_ORIENTATION_HORIZONTAL)
|
||||
gtk_extended_layout_get_height_for_width (GTK_EXTENDED_LAYOUT (child->widget),
|
||||
child_size, &child_minimum, &child_natural);
|
||||
else /* (private->orientation == GTK_ORIENTATION_VERTICAL) */
|
||||
gtk_extended_layout_get_width_for_height (GTK_EXTENDED_LAYOUT (child->widget),
|
||||
child_size, &child_minimum, &child_natural);
|
||||
|
||||
|
||||
computed_minimum = MAX (computed_minimum, child_minimum);
|
||||
computed_natural = MAX (computed_natural, child_natural);
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
computed_minimum += border_width * 2;
|
||||
computed_natural += border_width * 2;
|
||||
|
||||
if (minimum_size)
|
||||
*minimum_size = minimum;
|
||||
|
||||
*minimum_size = computed_minimum;
|
||||
if (natural_size)
|
||||
*natural_size = natural;
|
||||
|
||||
*natural_size = computed_natural;
|
||||
}
|
||||
|
||||
static void
|
||||
@ -1103,10 +1160,10 @@ gtk_box_get_width_for_height (GtkExtendedLayout *layout,
|
||||
|
||||
if (private->orientation == GTK_ORIENTATION_VERTICAL)
|
||||
{
|
||||
#if 0
|
||||
#if __I_HAD_A_MILLION_DOLLARS__
|
||||
gtk_box_compute_size_for_opposing_orientation (box, height, minimum_width, natural_width);
|
||||
#else
|
||||
/* Return the defaults instead of calculating in the opposing direction */
|
||||
/* Return the defaults instead of calculating in the opposing orientation */
|
||||
gtk_extended_layout_get_desired_width (layout, minimum_width, natural_width);
|
||||
#endif
|
||||
}
|
||||
@ -1124,14 +1181,7 @@ gtk_box_get_height_for_width (GtkExtendedLayout *layout,
|
||||
GtkBoxPrivate *private = GTK_BOX_GET_PRIVATE (layout);
|
||||
|
||||
if (private->orientation == GTK_ORIENTATION_HORIZONTAL)
|
||||
{
|
||||
#if 0
|
||||
gtk_box_compute_size_for_opposing_orientation (box, width, minimum_height, natural_height);
|
||||
#else
|
||||
/* Return the defaults instead of calculating in the opposing direction */
|
||||
gtk_extended_layout_get_desired_height (layout, minimum_height, natural_height);
|
||||
#endif
|
||||
}
|
||||
gtk_box_compute_size_for_opposing_orientation (box, width, minimum_height, natural_height);
|
||||
else
|
||||
gtk_box_compute_size_for_orientation (box, width, minimum_height, natural_height);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user