diff --git a/src/st/st-box-layout.c b/src/st/st-box-layout.c index 85fff5df7..af318435e 100644 --- a/src/st/st-box-layout.c +++ b/src/st/st-box-layout.c @@ -34,6 +34,8 @@ * */ +#include + #include "st-box-layout.h" #include "st-private.h" @@ -571,6 +573,125 @@ st_box_layout_get_preferred_height (ClutterActor *actor, min_height_p, natural_height_p); } +typedef struct { + int child_index; + gfloat shrink_amount; +} BoxChildShrink; + +/* Sort with the greatest shrink amount first */ +static int +compare_by_shrink_amount (const void *a, + const void *b) +{ + float diff = ((const BoxChildShrink *)a)->shrink_amount - ((const BoxChildShrink *)b)->shrink_amount; + return diff < 0 ? 1 : (diff == 0 ? 0 : -1); +} + +/* Sort in ascending order by child index */ +static int +compare_by_child_index (const void *a, + const void *b) +{ + return ((const BoxChildShrink *)a)->child_index - ((const BoxChildShrink *)b)->child_index; +} + +static BoxChildShrink * +compute_shrinks (StBoxLayout *self, + gfloat for_length, + gfloat total_shrink) +{ + StBoxLayoutPrivate *priv = self->priv; + int n_children = g_list_length (priv->children); + BoxChildShrink *shrinks = g_new0 (BoxChildShrink, n_children); + gfloat shrink_so_far, base_shrink; + int n_shrink_children; + GList *l; + int i; + + /* The effect that we want is that all the children get an equal chance + * to expand from their minimum size up to the natural size. Or to put + * it a different way, we want to start by shrinking only the child that + * can shrink most, then shrink that and the next most shrinkable child, + * to the point where we are shrinling everything. + */ + + /* Find the amount of possible shrink for each child */ + int n_visible_children = 0; + for (l = priv->children; l; l = l->next, i++) + { + gfloat child_min, child_nat; + + shrinks[i].child_index = i; + if (CLUTTER_ACTOR_IS_VISIBLE (l->data)) + { + if (priv->is_vertical) + clutter_actor_get_preferred_height ((ClutterActor*) l->data, + for_length, + &child_min, &child_nat); + else + clutter_actor_get_preferred_width ((ClutterActor*) l->data, + for_length, + &child_min, &child_nat); + + shrinks[i].shrink_amount = MAX (0., child_nat - child_min); + n_visible_children++; + } + else + { + shrinks[i].shrink_amount = -1.; + } + } + + /* We want to process children starting from the child with the maximum available + * shrink, so sort in this order; !visible children end up at the end */ + qsort (shrinks, n_children, sizeof (BoxChildShrink), compare_by_shrink_amount); + + /* +--+ + * | | + * | | +-- + * | | | | + * | | | | +-+ + * --+--+-+-+-+-+---------- + * | | | | | | +-+ +-+ + * | | | | | | | | | | + * --+--+-+-+-+-+-+-+------ + * + * We are trying to find the correct position for the upper line the "water mark" + * so that total of the portion of the bars above the line is equal to the total + * amount we want to shrink. + */ + + /* Start by moving the line downward, top-of-bar by top-of-bar */ + shrink_so_far = 0; + for (n_shrink_children = 1; n_shrink_children <= n_visible_children; n_shrink_children++) + { + if (n_shrink_children < n_visible_children) + base_shrink = shrinks[n_shrink_children].shrink_amount; + else + base_shrink = 0; + shrink_so_far += n_shrink_children * (shrinks[n_shrink_children - 1].shrink_amount - base_shrink); + + if (shrink_so_far >= total_shrink || n_shrink_children == n_visible_children) + break; + } + + /* OK, we found enough shrinkage, move it back upwards to the right position */ + base_shrink += (shrink_so_far - total_shrink) / n_shrink_children; + if (base_shrink < 0) /* can't shrink that much, probably round-off error */ + base_shrink = 0; + + /* Assign the portion above the base shrink line to the shrink_amount */ + for (i = 0; i < n_shrink_children; i++) + shrinks[i].shrink_amount -= base_shrink; + for (; i < n_children; i++) + shrinks[i].shrink_amount = 0; + + /* And sort back to their original order */ + qsort (shrinks, n_children, sizeof (BoxChildShrink), compare_by_child_index); + + return shrinks; +} + static void st_box_layout_allocate (ClutterActor *actor, const ClutterActorBox *box, @@ -579,10 +700,12 @@ st_box_layout_allocate (ClutterActor *actor, StBoxLayoutPrivate *priv = ST_BOX_LAYOUT (actor)->priv; StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor)); ClutterActorBox content_box; - gfloat avail_width, avail_height, pref_width, pref_height; - gfloat position = 0; + gfloat avail_width, avail_height, min_width, natural_width, min_height, natural_height; + gfloat position, next_position; GList *l; - gint n_expand_children, extra_space; + gint n_expand_children = 0, i; + gfloat expand_amount, shrink_amount; + BoxChildShrink *shrinks = NULL; CLUTTER_ACTOR_CLASS (st_box_layout_parent_class)->allocate (actor, box, flags); @@ -596,9 +719,9 @@ st_box_layout_allocate (ClutterActor *actor, avail_height = content_box.y2 - content_box.y1; get_content_preferred_height (ST_BOX_LAYOUT (actor), avail_width, - NULL, &pref_height); + &min_height, &natural_height); get_content_preferred_width (ST_BOX_LAYOUT (actor), avail_height, - NULL, &pref_width); + &min_width, &natural_width); /* update adjustments for scrolling */ if (priv->vadjustment) @@ -607,7 +730,7 @@ st_box_layout_allocate (ClutterActor *actor, g_object_set (G_OBJECT (priv->vadjustment), "lower", 0.0, - "upper", pref_height, + "upper", natural_height, "page-size", avail_height, "step-increment", avail_height / 6, "page-increment", avail_height, @@ -623,7 +746,7 @@ st_box_layout_allocate (ClutterActor *actor, g_object_set (G_OBJECT (priv->hadjustment), "lower", 0.0, - "upper", pref_width, + "upper", natural_width, "page-size", avail_width, "step-increment", avail_width / 6, "page-increment", avail_width, @@ -633,62 +756,72 @@ st_box_layout_allocate (ClutterActor *actor, st_adjustment_set_value (priv->hadjustment, prev_value); } - /* count the number of children with expand set to TRUE */ - n_expand_children = 0; - for (l = priv->children; l; l = l->next) + if (priv->is_vertical) { - gboolean expand; - - if (!CLUTTER_ACTOR_IS_VISIBLE (l->data)) - continue; - - clutter_container_child_get ((ClutterContainer *) actor, - (ClutterActor*) l->data, - "expand", &expand, - NULL); - if (expand) - n_expand_children++; - } - - if (n_expand_children == 0) - { - extra_space = 0; - n_expand_children = 1; + expand_amount = MAX (0, avail_height - natural_height); + shrink_amount = MAX (0, natural_height - MAX (avail_height, min_height)); } else { - if (priv->is_vertical) - extra_space = (avail_height - pref_height) / n_expand_children; - else - extra_space = (avail_width - pref_width) / n_expand_children; - - /* don't shrink anything */ - if (extra_space < 0) - extra_space = 0; + expand_amount = MAX (0, avail_width - natural_width); + shrink_amount = MAX (0, natural_width - MAX (avail_width, min_width)); } + if (expand_amount > 0) + { + /* count the number of children with expand set to TRUE */ + n_expand_children = 0; + for (l = priv->children; l; l = l->next) + { + gboolean expand; + + if (!CLUTTER_ACTOR_IS_VISIBLE (l->data)) + continue; + + clutter_container_child_get ((ClutterContainer *) actor, + (ClutterActor*) l->data, + "expand", &expand, + NULL); + if (expand) + n_expand_children++; + } + + if (n_expand_children == 0) + expand_amount = 0; + } + else if (shrink_amount > 0) + { + shrinks = compute_shrinks (ST_BOX_LAYOUT (actor), + priv->is_vertical ? avail_width : avail_height, + shrink_amount); + } + if (priv->is_vertical) position = content_box.y1; else position = content_box.x1; if (priv->is_pack_start) - l = g_list_last (priv->children); + { + l = g_list_last (priv->children); + i = g_list_length (priv->children); + } else - l = priv->children; + { + l = priv->children; + i = 0; + } - for (l = (priv->is_pack_start) ? g_list_last (priv->children) : priv->children; - l; - l = (priv->is_pack_start) ? l->prev : l->next) + while (l) { ClutterActor *child = (ClutterActor*) l->data; ClutterActorBox child_box; - gfloat child_nat; + gfloat child_min, child_nat, child_allocated; gboolean xfill, yfill, expand; StAlign xalign, yalign; if (!CLUTTER_ACTOR_IS_VISIBLE (child)) - continue; + goto next_child; clutter_container_child_get ((ClutterContainer*) actor, child, "x-fill", &xfill, @@ -701,48 +834,61 @@ st_box_layout_allocate (ClutterActor *actor, if (priv->is_vertical) { clutter_actor_get_preferred_height (child, avail_width, - NULL, &child_nat); + &child_min, &child_nat); + } + else + { + clutter_actor_get_preferred_width (child, avail_height, + &child_min, &child_nat); + } - child_box.y1 = position; - if (expand) - child_box.y2 = position + child_nat + extra_space; - else - child_box.y2 = position + child_nat; + child_allocated = child_nat; + if (expand_amount > 0 && expand) + child_allocated += expand_amount / n_expand_children; + else if (shrink_amount > 0) + child_allocated -= shrinks[i].shrink_amount; + + next_position = position + child_allocated; + + if (priv->is_vertical) + { + child_box.y1 = (int)(0.5 + position); + child_box.y2 = (int)(0.5 + next_position); child_box.x1 = content_box.x1; child_box.x2 = content_box.x2; _st_allocate_fill (child, &child_box, xalign, yalign, xfill, yfill); clutter_actor_allocate (child, &child_box, flags); - if (expand) - position += (child_nat + priv->spacing + extra_space); - else - position += (child_nat + priv->spacing); } else { - - clutter_actor_get_preferred_width (child, avail_height, - NULL, &child_nat); - - child_box.x1 = position; - - if (expand) - child_box.x2 = position + child_nat + extra_space; - else - child_box.x2 = position + child_nat; - + child_box.x1 = (int)(0.5 + position); + child_box.x2 = (int)(0.5 + next_position); child_box.y1 = content_box.y1; child_box.y2 = content_box.y2; + _st_allocate_fill (child, &child_box, xalign, yalign, xfill, yfill); clutter_actor_allocate (child, &child_box, flags); + } - if (expand) - position += (child_nat + priv->spacing + extra_space); - else - position += (child_nat + priv->spacing); + position = next_position + priv->spacing; + + next_child: + if (priv->is_pack_start) + { + l = l->prev; + i--; + } + else + { + l = l->next; + i++; } } + + if (shrinks) + g_free (shrinks); } static void diff --git a/tests/interactive/box-layout.js b/tests/interactive/box-layout.js index ae4db6986..2b8dda856 100644 --- a/tests/interactive/box-layout.js +++ b/tests/interactive/box-layout.js @@ -8,32 +8,79 @@ const UI = imports.testcommon.ui; UI.init(); let stage = Clutter.Stage.get_default(); -let b = new St.BoxLayout({ vertical: true, - width: stage.width, - height: stage.height }); -stage.add_actor(b); +let vbox = new St.BoxLayout({ vertical: true, + width: stage.width, + height: stage.height, + spacing: 10, + style: 'padding: 10px' }); +stage.add_actor(vbox); -let b2 = new St.BoxLayout(); -b.add(b2, { expand: true, fill: true }); +//////////////////////////////////////////////////////////////////////////////// -let r1 = new St.BoxLayout({ style_class: 'red', - width: 10, - height: 10 }); -b2.add(r1, { expand: true }); +let colored_boxes = new St.BoxLayout({ vertical: true, + width: 200, + height: 200, + style: 'border: 2px solid black;' }); +vbox.add(colored_boxes, { x_fill: false, + x_align: St.Align.MIDDLE }); -let r2 = new St.BoxLayout({ style_class: 'green', - width: 10, - height: 10 }); -b2.add(r2, { expand: true, - x_fill: false, - x_align: St.Align.MIDDLE, - y_fill: false, - y_align: St.Align.MIDDLE }); +let b2 = new St.BoxLayout({ style: 'border: 2px solid #666666' }); +colored_boxes.add(b2, { expand: true }); -let r3 = new St.BoxLayout({ style_class: 'blue', - width: 10, - height: 10 }); -b.add(r3); +b2.add(new St.Label({ text: "Expand", + style: 'border: 1px solid #aaaaaa; ' + + 'background: #ffeecc' }), + { expand: true }); +b2.add(new St.Label({ text: "Expand\nNo Fill", + style: 'border: 1px solid #aaaaaa; ' + + 'background: #ccffaa' }), + { expand: true, + x_fill: false, + x_align: St.Align.MIDDLE, + y_fill: false, + y_align: St.Align.MIDDLE }); + +colored_boxes.add(new St.Label({ text: "Default", + style: 'border: 1px solid #aaaaaa; ' + + 'background: #cceeff' })); + +//////////////////////////////////////////////////////////////////////////////// + +function createCollapsableBox(width) { + let b = new St.BoxLayout({ width: width, + style: 'border: 1px solid black;' + + 'font: 13px Sans;' }); + b.add(new St.Label({ text: "Very Very Very Long", + style: 'background: #ffaacc;' + + 'padding: 5px; ' + + 'border: 1px solid #666666;' }), + { expand: true }); + b.add(new St.Label({ text: "Very Very Long", + style: 'background: #ffeecc; ' + + 'padding: 5px; ' + + 'border: 1px solid #666666;' }), + { expand: true }); + b.add(new St.Label({ text: "Very Long", + style: 'background: #ccffaa; ' + + 'padding: 5px; ' + + 'border: 1px solid #666666;' }), + { expand: true }); + b.add(new St.Label({ text: "Short", + style: 'background: #cceeff; ' + + 'padding: 5px; ' + + 'border: 1px solid #666666;' }), + { expand: true }); + + return b; +} + +for (let width = 200; width <= 500; width += 60 ) { + vbox.add(createCollapsableBox (width), + { x_fill: false, + x_align: St.Align.MIDDLE }); +} + +//////////////////////////////////////////////////////////////////////////////// stage.show(); Clutter.main();