1/* gtkboxlayout.c: Box layout manager
2 *
3 * Copyright 2019 GNOME Foundation
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19#include "config.h"
20
21#include "gtkboxlayout.h"
22
23#include "gtkcsspositionvalueprivate.h"
24#include "gtkintl.h"
25#include "gtkorientable.h"
26#include "gtkprivate.h"
27#include "gtksizerequest.h"
28#include "gtktypebuiltins.h"
29#include "gtkwidgetprivate.h"
30#include "gtkcssnodeprivate.h"
31
32/**
33 * GtkBoxLayout:
34 *
35 * `GtkBoxLayout` is a layout manager that arranges children in a single
36 * row or column.
37 *
38 * Whether it is a row or column depends on the value of its
39 * [property@Gtk.Orientable:orientation] property. Within the other dimension
40 * all children all allocated the same size. The `GtkBoxLayout` will respect
41 * the [property@Gtk.Widget:halign] and [property@Gtk.Widget:valign]
42 * properties of each child widget.
43 *
44 * If you want all children to be assigned the same size, you can use
45 * the [property@Gtk.BoxLayout:homogeneous] property.
46 *
47 * If you want to specify the amount of space placed between each child,
48 * you can use the [property@Gtk.BoxLayout:spacing] property.
49 */
50
51struct _GtkBoxLayout
52{
53 GtkLayoutManager parent_instance;
54
55 gboolean homogeneous;
56 guint spacing;
57 GtkOrientation orientation;
58 GtkBaselinePosition baseline_position;
59};
60
61G_DEFINE_TYPE_WITH_CODE (GtkBoxLayout, gtk_box_layout, GTK_TYPE_LAYOUT_MANAGER,
62 G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL))
63
64enum {
65 PROP_HOMOGENEOUS = 1,
66 PROP_SPACING,
67 PROP_BASELINE_POSITION,
68
69 /* From GtkOrientable */
70 PROP_ORIENTATION,
71
72 N_PROPS = PROP_ORIENTATION
73};
74
75static GParamSpec *box_layout_props[N_PROPS];
76
77static void
78gtk_box_layout_set_orientation (GtkBoxLayout *self,
79 GtkOrientation orientation)
80{
81 GtkLayoutManager *layout_manager = GTK_LAYOUT_MANAGER (ptr: self);
82 GtkWidget *widget;
83
84 if (self->orientation == orientation)
85 return;
86
87 self->orientation = orientation;
88
89 widget = gtk_layout_manager_get_widget (manager: layout_manager);
90 if (widget != NULL && GTK_IS_ORIENTABLE (widget))
91 gtk_widget_update_orientation (widget, orientation: self->orientation);
92
93 gtk_layout_manager_layout_changed (manager: layout_manager);
94
95 g_object_notify (G_OBJECT (self), property_name: "orientation");
96}
97
98static void
99gtk_box_layout_set_property (GObject *gobject,
100 guint prop_id,
101 const GValue *value,
102 GParamSpec *pspec)
103{
104 GtkBoxLayout *self = GTK_BOX_LAYOUT (ptr: gobject);
105
106 switch (prop_id)
107 {
108 case PROP_HOMOGENEOUS:
109 gtk_box_layout_set_homogeneous (box_layout: self, homogeneous: g_value_get_boolean (value));
110 break;
111
112 case PROP_SPACING:
113 gtk_box_layout_set_spacing (box_layout: self, spacing: g_value_get_int (value));
114 break;
115
116 case PROP_BASELINE_POSITION:
117 gtk_box_layout_set_baseline_position (box_layout: self, position: g_value_get_enum (value));
118 break;
119
120 case PROP_ORIENTATION:
121 gtk_box_layout_set_orientation (self, orientation: g_value_get_enum (value));
122 break;
123
124 default:
125 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
126 break;
127 }
128}
129
130static void
131gtk_box_layout_get_property (GObject *gobject,
132 guint prop_id,
133 GValue *value,
134 GParamSpec *pspec)
135{
136 GtkBoxLayout *box_layout = GTK_BOX_LAYOUT (ptr: gobject);
137
138 switch (prop_id)
139 {
140 case PROP_HOMOGENEOUS:
141 g_value_set_boolean (value, v_boolean: box_layout->homogeneous);
142 break;
143
144 case PROP_SPACING:
145 g_value_set_int (value, v_int: box_layout->spacing);
146 break;
147
148 case PROP_BASELINE_POSITION:
149 g_value_set_enum (value, v_enum: box_layout->baseline_position);
150 break;
151
152 case PROP_ORIENTATION:
153 g_value_set_enum (value, v_enum: box_layout->orientation);
154 break;
155
156 default:
157 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
158 break;
159 }
160}
161
162static void
163count_expand_children (GtkWidget *widget,
164 GtkOrientation orientation,
165 int *visible_children,
166 int *expand_children)
167{
168 GtkWidget *child;
169
170 *visible_children = *expand_children = 0;
171
172 for (child = _gtk_widget_get_first_child (widget);
173 child != NULL;
174 child = _gtk_widget_get_next_sibling (widget: child))
175 {
176 if (!gtk_widget_should_layout (widget: child))
177 continue;
178
179 *visible_children += 1;
180
181 if (gtk_widget_compute_expand (widget: child, orientation))
182 *expand_children += 1;
183 }
184}
185
186static int
187get_spacing (GtkBoxLayout *self,
188 GtkCssNode *node)
189{
190 GtkCssStyle *style = gtk_css_node_get_style (cssnode: node);
191 GtkCssValue *border_spacing;
192 int css_spacing;
193
194 border_spacing = style->size->border_spacing;
195 if (self->orientation == GTK_ORIENTATION_HORIZONTAL)
196 css_spacing = _gtk_css_position_value_get_x (position: border_spacing, one_hundred_percent: 100);
197 else
198 css_spacing = _gtk_css_position_value_get_y (position: border_spacing, one_hundred_percent: 100);
199
200 return css_spacing + self->spacing;
201}
202
203static void
204gtk_box_layout_compute_size (GtkBoxLayout *self,
205 GtkWidget *widget,
206 int for_size,
207 int *minimum,
208 int *natural)
209{
210 GtkWidget *child;
211 int n_visible_children = 0;
212 int required_min = 0, required_nat = 0;
213 int largest_min = 0, largest_nat = 0;
214 int spacing = get_spacing (self, node: gtk_widget_get_css_node (widget));
215
216 for (child = gtk_widget_get_first_child (widget);
217 child != NULL;
218 child = gtk_widget_get_next_sibling (widget: child))
219 {
220 int child_min = 0;
221 int child_nat = 0;
222
223 if (!gtk_widget_should_layout (widget: child))
224 continue;
225
226 gtk_widget_measure (widget: child, orientation: self->orientation,
227 for_size,
228 minimum: &child_min, natural: &child_nat,
229 NULL, NULL);
230
231 largest_min = MAX (largest_min, child_min);
232 largest_nat = MAX (largest_nat, child_nat);
233
234 required_min += child_min;
235 required_nat += child_nat;
236
237 n_visible_children += 1;
238 }
239
240 if (n_visible_children > 0)
241 {
242 if (self->homogeneous)
243 {
244 required_min = largest_min * n_visible_children;
245 required_nat = largest_nat * n_visible_children;
246 }
247
248 required_min += (n_visible_children - 1) * spacing;
249 required_nat += (n_visible_children - 1) * spacing;
250 }
251
252 *minimum = required_min;
253 *natural = required_nat;
254}
255
256static void
257gtk_box_layout_compute_opposite_size (GtkBoxLayout *self,
258 GtkWidget *widget,
259 int *minimum,
260 int *natural,
261 int *min_baseline,
262 int *nat_baseline)
263{
264 GtkWidget *child;
265 int largest_min = 0, largest_nat = 0;
266
267 for (child = gtk_widget_get_first_child (widget);
268 child != NULL;
269 child = gtk_widget_get_next_sibling (widget: child))
270 {
271 int child_min = 0;
272 int child_nat = 0;
273
274 if (!gtk_widget_should_layout (widget: child))
275 continue;
276
277 gtk_widget_measure (widget: child,
278 OPPOSITE_ORIENTATION (self->orientation),
279 for_size: -1,
280 minimum: &child_min, natural: &child_nat,
281 NULL, NULL);
282
283 largest_min = MAX (largest_min, child_min);
284 largest_nat = MAX (largest_nat, child_nat);
285 }
286
287 *minimum = largest_min;
288 *natural = largest_nat;
289}
290
291/* if widgets haven't reached their min opposite size at this
292 * huge value, things went massively wrong and we need to bail to not
293 * cause an infinite loop.
294 */
295#define MAX_ALLOWED_SIZE (1 << 20)
296
297static int
298distribute_remaining_size (GtkRequestedSize *sizes,
299 gsize n_sizes,
300 GtkOrientation orientation,
301 int available,
302 int min,
303 int max)
304{
305 int total_size = 0;
306 gsize i;
307
308 if (n_sizes == 0)
309 return available;
310
311 for (i = 0; i < n_sizes; i++)
312 {
313 gtk_widget_measure (widget: sizes[i].data,
314 orientation,
315 for_size: min,
316 minimum: &sizes[i].minimum_size, natural: &sizes[i].natural_size,
317 NULL, NULL);
318 total_size += sizes[i].minimum_size;
319 }
320
321 if (total_size <= available)
322 return available - total_size;
323
324 /* total_size > available happens when we last ran for values too big,
325 * rerun for the correct value min == max in that case */
326 while (min < max || total_size > available)
327 {
328 int test;
329
330 if (min > MAX_ALLOWED_SIZE)
331 {
332 /* sanity check! */
333 for (i = 0; i < n_sizes; i++)
334 {
335 int check_min, check_nat;
336 gtk_widget_measure (widget: sizes[i].data,
337 orientation,
338 MAX_ALLOWED_SIZE,
339 minimum: &sizes[i].minimum_size, natural: &sizes[i].natural_size,
340 NULL, NULL);
341 gtk_widget_measure (widget: sizes[i].data,
342 orientation,
343 for_size: -1,
344 minimum: &check_min, natural: &check_nat,
345 NULL, NULL);
346 if (check_min < sizes[i].minimum_size)
347 {
348 g_critical ("%s %p reports a minimum %s of %u, but minimum %s for %s of %u is %u. Expect overlapping widgets.",
349 G_OBJECT_TYPE_NAME (sizes[i].data), sizes[i].data,
350 orientation == GTK_ORIENTATION_HORIZONTAL ? "width" : "height",
351 check_min,
352 orientation == GTK_ORIENTATION_HORIZONTAL ? "width" : "height",
353 orientation == GTK_ORIENTATION_HORIZONTAL ? "height" : "width",
354 MAX_ALLOWED_SIZE, sizes[i].minimum_size);
355 sizes[i].minimum_size = check_min;
356 sizes[i].natural_size = check_nat;
357 }
358 total_size += sizes[i].minimum_size;
359 }
360 return MAX (0, available - total_size);
361 }
362
363 if (max == MAX_ALLOWED_SIZE)
364 test = min * 2;
365 else
366 test = (min + max) / 2;
367
368 total_size = 0;
369 for (i = 0; i < n_sizes; i++)
370 {
371 gtk_widget_measure (widget: sizes[i].data,
372 orientation,
373 for_size: test,
374 minimum: &sizes[i].minimum_size, natural: &sizes[i].natural_size,
375 NULL, NULL);
376 total_size += sizes[i].minimum_size;
377 }
378
379 if (total_size > available)
380 min = test + 1;
381 else
382 max = test;
383 }
384
385 return available - total_size;
386}
387
388static void
389gtk_box_layout_compute_opposite_size_for_size (GtkBoxLayout *self,
390 GtkWidget *widget,
391 int for_size,
392 int *minimum,
393 int *natural,
394 int *min_baseline,
395 int *nat_baseline)
396{
397 GtkWidget *child;
398 int nvis_children;
399 int nexpand_children;
400 int computed_minimum = 0, computed_natural = 0;
401 int computed_minimum_above = 0, computed_natural_above = 0;
402 int computed_minimum_below = 0, computed_natural_below = 0;
403 int computed_minimum_baseline = -1, computed_natural_baseline = -1;
404 GtkRequestedSize *sizes;
405 int available, size_given_to_child, i;
406 int child_size, child_minimum, child_natural;
407 int child_minimum_baseline, child_natural_baseline;
408 int n_extra_widgets = 0;
409 int spacing;
410 gboolean have_baseline = FALSE;
411
412 count_expand_children (widget, orientation: self->orientation, visible_children: &nvis_children, expand_children: &nexpand_children);
413
414 if (nvis_children <= 0)
415 return;
416
417 spacing = get_spacing (self, node: gtk_widget_get_css_node (widget));
418 sizes = g_newa (GtkRequestedSize, nvis_children);
419 g_assert ((nvis_children - 1) * spacing <= for_size);
420 available = for_size - (nvis_children - 1) * spacing;
421
422 if (self->homogeneous)
423 {
424 size_given_to_child = available / nvis_children;
425 n_extra_widgets = available % nvis_children;
426
427 for (child = _gtk_widget_get_first_child (widget);
428 child != NULL;
429 child = _gtk_widget_get_next_sibling (widget: child))
430 {
431 if (!gtk_widget_should_layout (widget: child))
432 continue;
433
434 child_size = size_given_to_child;
435 if (n_extra_widgets)
436 {
437 child_size++;
438 n_extra_widgets--;
439 }
440
441 child_minimum_baseline = child_natural_baseline = -1;
442 /* Assign the child's position. */
443 gtk_widget_measure (widget: child,
444 OPPOSITE_ORIENTATION (self->orientation),
445 for_size: child_size,
446 minimum: &child_minimum, natural: &child_natural,
447 minimum_baseline: &child_minimum_baseline, natural_baseline: &child_natural_baseline);
448
449 if (child_minimum_baseline >= 0)
450 {
451 have_baseline = TRUE;
452 computed_minimum_below = MAX (computed_minimum_below, child_minimum - child_minimum_baseline);
453 computed_natural_below = MAX (computed_natural_below, child_natural - child_natural_baseline);
454 computed_minimum_above = MAX (computed_minimum_above, child_minimum_baseline);
455 computed_natural_above = MAX (computed_natural_above, child_natural_baseline);
456 }
457 else
458 {
459 computed_minimum = MAX (computed_minimum, child_minimum);
460 computed_natural = MAX (computed_natural, child_natural);
461 }
462 }
463 }
464 else
465 {
466 int min_size = 0, child_min_size;
467 int n_inconstant = 0;
468
469 /* Retrieve desired size for visible children */
470 for (i = 0, child = _gtk_widget_get_first_child (widget);
471 child != NULL;
472 child = _gtk_widget_get_next_sibling (widget: child))
473 {
474 if (!gtk_widget_should_layout (widget: child))
475 continue;
476
477 if (gtk_widget_get_request_mode (widget: child) == GTK_SIZE_REQUEST_CONSTANT_SIZE)
478 {
479 gtk_widget_measure (widget: child,
480 orientation: self->orientation,
481 for_size: -1,
482 minimum: &sizes[i].minimum_size, natural: &sizes[i].natural_size,
483 NULL, NULL);
484 sizes[i].data = child;
485 g_assert (available >= sizes[i].minimum_size);
486 available -= sizes[i].minimum_size;
487 i++;
488 }
489 else
490 {
491 gtk_widget_measure (widget: child,
492 OPPOSITE_ORIENTATION (self->orientation),
493 for_size: -1,
494 minimum: &child_min_size, NULL,
495 NULL, NULL);
496 min_size = MAX (min_size, child_min_size);
497 n_inconstant++;
498 sizes[nvis_children - n_inconstant].data = child;
499 }
500 }
501
502 available = distribute_remaining_size (sizes: sizes + nvis_children - n_inconstant,
503 n_sizes: n_inconstant,
504 orientation: self->orientation,
505 available,
506 min: min_size,
507 MAX_ALLOWED_SIZE);
508
509 /* Bring children up to size first */
510 available = gtk_distribute_natural_allocation (extra_space: available, n_requested_sizes: nvis_children, sizes);
511
512 /* Calculate space which hasn't distributed yet,
513 * and is available for expanding children.
514 */
515 if (nexpand_children > 0)
516 {
517 size_given_to_child = available / nexpand_children;
518 n_extra_widgets = available % nexpand_children;
519 }
520 else
521 {
522 size_given_to_child = 0;
523 }
524
525 i = 0;
526 n_inconstant = 0;
527 for (child = _gtk_widget_get_first_child (widget);
528 child != NULL;
529 child = _gtk_widget_get_next_sibling (widget: child))
530 {
531 if (!gtk_widget_should_layout (widget: child))
532 continue;
533
534 if (sizes[i].data == child)
535 {
536 child_size = sizes[i].minimum_size;
537 i++;
538 }
539 else
540 {
541 n_inconstant++;
542 g_assert (sizes[nvis_children - n_inconstant].data == child);
543 child_size = sizes[nvis_children - n_inconstant].minimum_size;
544 }
545
546 if (gtk_widget_compute_expand (widget: child, orientation: self->orientation))
547 {
548 child_size += size_given_to_child;
549
550 if (n_extra_widgets > 0)
551 {
552 child_size++;
553 n_extra_widgets--;
554 }
555 }
556
557 child_minimum_baseline = child_natural_baseline = -1;
558 /* Assign the child's position. */
559 gtk_widget_measure (widget: child,
560 OPPOSITE_ORIENTATION (self->orientation),
561 for_size: child_size,
562 minimum: &child_minimum, natural: &child_natural,
563 minimum_baseline: &child_minimum_baseline, natural_baseline: &child_natural_baseline);
564
565 if (child_minimum_baseline >= 0)
566 {
567 have_baseline = TRUE;
568 computed_minimum_below = MAX (computed_minimum_below, child_minimum - child_minimum_baseline);
569 computed_natural_below = MAX (computed_natural_below, child_natural - child_natural_baseline);
570 computed_minimum_above = MAX (computed_minimum_above, child_minimum_baseline);
571 computed_natural_above = MAX (computed_natural_above, child_natural_baseline);
572 }
573 else
574 {
575 computed_minimum = MAX (computed_minimum, child_minimum);
576 computed_natural = MAX (computed_natural, child_natural);
577 }
578 }
579 }
580
581 if (have_baseline)
582 {
583 computed_minimum = MAX (computed_minimum, computed_minimum_below + computed_minimum_above);
584 computed_natural = MAX (computed_natural, computed_natural_below + computed_natural_above);
585 switch (self->baseline_position)
586 {
587 case GTK_BASELINE_POSITION_TOP:
588 computed_minimum_baseline = computed_minimum_above;
589 computed_natural_baseline = computed_natural_above;
590 break;
591 case GTK_BASELINE_POSITION_CENTER:
592 computed_minimum_baseline = computed_minimum_above + MAX((computed_minimum - (computed_minimum_above + computed_minimum_below)) / 2, 0);
593 computed_natural_baseline = computed_natural_above + MAX((computed_natural - (computed_natural_above + computed_natural_below)) / 2, 0);
594 break;
595 case GTK_BASELINE_POSITION_BOTTOM:
596 computed_minimum_baseline = computed_minimum - computed_minimum_below;
597 computed_natural_baseline = computed_natural - computed_natural_below;
598 break;
599 default:
600 break;
601 }
602 }
603
604 *minimum = computed_minimum;
605 *natural = MAX (computed_natural, computed_natural_below + computed_natural_above);
606 *min_baseline = computed_minimum_baseline;
607 *nat_baseline = computed_natural_baseline;
608}
609
610static void
611gtk_box_layout_measure (GtkLayoutManager *layout_manager,
612 GtkWidget *widget,
613 GtkOrientation orientation,
614 int for_size,
615 int *minimum,
616 int *natural,
617 int *min_baseline,
618 int *nat_baseline)
619{
620 GtkBoxLayout *self = GTK_BOX_LAYOUT (ptr: layout_manager);
621
622 if (self->orientation != orientation)
623 {
624 if (for_size < 0)
625 {
626 gtk_box_layout_compute_opposite_size (self, widget,
627 minimum, natural,
628 min_baseline, nat_baseline);
629 }
630 else
631 {
632 gtk_box_layout_compute_opposite_size_for_size (self, widget, for_size,
633 minimum, natural,
634 min_baseline, nat_baseline);
635 }
636 }
637 else
638 {
639 gtk_box_layout_compute_size (self, widget, for_size,
640 minimum, natural);
641 }
642}
643
644static void
645gtk_box_layout_allocate (GtkLayoutManager *layout_manager,
646 GtkWidget *widget,
647 int width,
648 int height,
649 int baseline)
650{
651 GtkBoxLayout *self = GTK_BOX_LAYOUT (ptr: layout_manager);
652 GtkWidget *child;
653 int nvis_children;
654 int nexpand_children;
655 GtkTextDirection direction;
656 GtkAllocation child_allocation;
657 GtkRequestedSize *sizes;
658 int child_minimum_baseline, child_natural_baseline;
659 int minimum_above, natural_above;
660 int minimum_below, natural_below;
661 gboolean have_baseline;
662 int extra_space;
663 int children_minimum_size = 0;
664 int size_given_to_child;
665 int n_extra_widgets = 0; /* Number of widgets that receive 1 extra px */
666 int x = 0, y = 0, i;
667 int child_size;
668 int spacing;
669
670 count_expand_children (widget, orientation: self->orientation, visible_children: &nvis_children, expand_children: &nexpand_children);
671
672 /* If there is no visible child, simply return. */
673 if (nvis_children <= 0)
674 return;
675
676 direction = _gtk_widget_get_direction (widget);
677 sizes = g_newa (GtkRequestedSize, nvis_children);
678 spacing = get_spacing (self, node: gtk_widget_get_css_node (widget));
679
680 if (self->orientation == GTK_ORIENTATION_HORIZONTAL)
681 extra_space = width - (nvis_children - 1) * spacing;
682 else
683 extra_space = height - (nvis_children - 1) * spacing;
684
685 have_baseline = FALSE;
686 minimum_above = natural_above = 0;
687 minimum_below = natural_below = 0;
688
689 /* Retrieve desired size for visible children. */
690 for (i = 0, child = _gtk_widget_get_first_child (widget);
691 child != NULL;
692 child = _gtk_widget_get_next_sibling (widget: child))
693 {
694 if (!gtk_widget_should_layout (widget: child))
695 continue;
696
697 gtk_widget_measure (widget: child,
698 orientation: self->orientation,
699 for_size: self->orientation == GTK_ORIENTATION_HORIZONTAL ? height : width,
700 minimum: &sizes[i].minimum_size, natural: &sizes[i].natural_size,
701 NULL, NULL);
702
703 children_minimum_size += sizes[i].minimum_size;
704
705 sizes[i].data = child;
706
707 i++;
708 }
709
710 if (self->homogeneous)
711 {
712 /* We still need to run the above loop to populate the minimum sizes for
713 * children that aren't going to fill.
714 */
715
716 size_given_to_child = extra_space / nvis_children;
717 n_extra_widgets = extra_space % nvis_children;
718 }
719 else
720 {
721 /* Bring children up to size first */
722 extra_space -= children_minimum_size;
723 extra_space = MAX (0, extra_space);
724 extra_space = gtk_distribute_natural_allocation (extra_space, n_requested_sizes: nvis_children, sizes);
725
726 /* Calculate space which hasn't distributed yet,
727 * and is available for expanding children.
728 */
729 if (nexpand_children > 0)
730 {
731 size_given_to_child = extra_space / nexpand_children;
732 n_extra_widgets = extra_space % nexpand_children;
733 }
734 else
735 {
736 size_given_to_child = 0;
737 }
738 }
739
740 /* Allocate child sizes. */
741 for (i = 0, child = _gtk_widget_get_first_child (widget);
742 child != NULL;
743 child = _gtk_widget_get_next_sibling (widget: child))
744 {
745 if (!gtk_widget_should_layout (widget: child))
746 continue;
747
748 /* Assign the child's size. */
749 if (self->homogeneous)
750 {
751 child_size = size_given_to_child;
752
753 if (n_extra_widgets > 0)
754 {
755 child_size++;
756 n_extra_widgets--;
757 }
758 }
759 else
760 {
761 child_size = sizes[i].minimum_size;
762
763 if (gtk_widget_compute_expand (widget: child, orientation: self->orientation))
764 {
765 child_size += size_given_to_child;
766
767 if (n_extra_widgets > 0)
768 {
769 child_size++;
770 n_extra_widgets--;
771 }
772 }
773 }
774
775 sizes[i].natural_size = child_size;
776
777 if (self->orientation == GTK_ORIENTATION_HORIZONTAL &&
778 gtk_widget_get_valign (widget: child) == GTK_ALIGN_BASELINE)
779 {
780 int child_allocation_width;
781 int child_minimum_height, child_natural_height;
782
783 child_allocation_width = child_size;
784
785 child_minimum_baseline = -1;
786 child_natural_baseline = -1;
787 gtk_widget_measure (widget: child, orientation: GTK_ORIENTATION_VERTICAL,
788 for_size: child_allocation_width,
789 minimum: &child_minimum_height, natural: &child_natural_height,
790 minimum_baseline: &child_minimum_baseline, natural_baseline: &child_natural_baseline);
791
792 if (child_minimum_baseline >= 0)
793 {
794 have_baseline = TRUE;
795 minimum_below = MAX (minimum_below, child_minimum_height - child_minimum_baseline);
796 natural_below = MAX (natural_below, child_natural_height - child_natural_baseline);
797 minimum_above = MAX (minimum_above, child_minimum_baseline);
798 natural_above = MAX (natural_above, child_natural_baseline);
799 }
800 }
801
802 i++;
803 }
804
805 if (self->orientation == GTK_ORIENTATION_VERTICAL)
806 baseline = -1;
807
808 /* we only calculate our own baseline if we don't get one passed from the parent
809 * and any of the child widgets explicitly request one */
810 if (baseline == -1 && have_baseline)
811 {
812 /* TODO: This is purely based on the minimum baseline, when things fit we should
813 use the natural one? */
814
815 switch (self->baseline_position)
816 {
817 case GTK_BASELINE_POSITION_TOP:
818 baseline = minimum_above;
819 break;
820 case GTK_BASELINE_POSITION_CENTER:
821 baseline = minimum_above + (height - (minimum_above + minimum_below)) / 2;
822 break;
823 case GTK_BASELINE_POSITION_BOTTOM:
824 baseline = height - minimum_below;
825 break;
826 default:
827 break;
828 }
829 }
830
831 /* Allocate child positions. */
832 if (self->orientation == GTK_ORIENTATION_HORIZONTAL)
833 {
834 child_allocation.y = 0;
835 child_allocation.height = height;
836 x = 0;
837 }
838 else
839 {
840 child_allocation.x = 0;
841 child_allocation.width = width;
842 y = 0;
843 }
844
845 for (i = 0, child = _gtk_widget_get_first_child (widget);
846 child != NULL;
847 child = _gtk_widget_get_next_sibling (widget: child))
848 {
849 if (!gtk_widget_should_layout (widget: child))
850 continue;
851
852 child_size = sizes[i].natural_size;
853
854 /* Assign the child's position. */
855 if (self->orientation == GTK_ORIENTATION_HORIZONTAL)
856 {
857 child_allocation.width = child_size;
858 child_allocation.x = x;
859
860 x += child_size + spacing;
861
862 if (direction == GTK_TEXT_DIR_RTL)
863 child_allocation.x = width - child_allocation.x - child_allocation.width;
864
865 }
866 else /* (self->orientation == GTK_ORIENTATION_VERTICAL) */
867 {
868 child_allocation.height = child_size;
869 child_allocation.y = y;
870
871 y += child_size + spacing;
872 }
873
874 gtk_widget_size_allocate (widget: child, allocation: &child_allocation, baseline);
875
876 i++;
877 }
878}
879
880static void
881gtk_box_layout_class_init (GtkBoxLayoutClass *klass)
882{
883 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
884 GtkLayoutManagerClass *layout_manager_class = GTK_LAYOUT_MANAGER_CLASS (ptr: klass);
885
886 gobject_class->set_property = gtk_box_layout_set_property;
887 gobject_class->get_property = gtk_box_layout_get_property;
888
889 layout_manager_class->measure = gtk_box_layout_measure;
890 layout_manager_class->allocate = gtk_box_layout_allocate;
891
892 /**
893 * GtkBoxLayout:homogeneous: (attributes org.gtk.Property.get=gtk_box_layout_get_homogeneous org.gtk.Property.set=gtk_box_layout_set_homogeneous)
894 *
895 * Whether the box layout should distribute the available space
896 * equally among the children.
897 */
898 box_layout_props[PROP_HOMOGENEOUS] =
899 g_param_spec_boolean (name: "homogeneous",
900 P_("Homogeneous"),
901 P_("Distribute space homogeneously"),
902 FALSE,
903 GTK_PARAM_READWRITE |
904 G_PARAM_EXPLICIT_NOTIFY);
905
906 /**
907 * GtkBoxLayout:spacing: (attributes org.gtk.Property.get=gtk_box_layout_get_spacing org.gtk.Property.set=gtk_box_layout_set_spacing)
908 *
909 * The space to put between the children.
910 */
911 box_layout_props[PROP_SPACING] =
912 g_param_spec_int (name: "spacing",
913 P_("Spacing"),
914 P_("Spacing between widgets"),
915 minimum: 0, G_MAXINT, default_value: 0,
916 GTK_PARAM_READWRITE |
917 G_PARAM_EXPLICIT_NOTIFY);
918
919 /**
920 * GtkBoxLayout:baseline-position: (attributes org.gtk.Property.get=gtk_box_layout_get_baseline_position org.gtk.Property.set=gtk_box_layout_set_baseline_position)
921 *
922 * The position of the allocated baseline within the extra space
923 * allocated to each child.
924 *
925 * This property is only relevant for horizontal layouts containing
926 * at least one child with a baseline alignment.
927 */
928 box_layout_props[PROP_BASELINE_POSITION] =
929 g_param_spec_enum (name: "baseline-position",
930 P_("Baseline position"),
931 P_("The position of the baseline aligned widgets if extra space is available"),
932 enum_type: GTK_TYPE_BASELINE_POSITION,
933 default_value: GTK_BASELINE_POSITION_CENTER,
934 GTK_PARAM_READWRITE |
935 G_PARAM_EXPLICIT_NOTIFY);
936
937 g_object_class_install_properties (oclass: gobject_class, n_pspecs: N_PROPS, pspecs: box_layout_props);
938 g_object_class_override_property (oclass: gobject_class, property_id: PROP_ORIENTATION, name: "orientation");
939}
940
941static void
942gtk_box_layout_init (GtkBoxLayout *self)
943{
944 self->homogeneous = FALSE;
945 self->spacing = 0;
946 self->orientation = GTK_ORIENTATION_HORIZONTAL;
947 self->baseline_position = GTK_BASELINE_POSITION_CENTER;
948}
949
950/**
951 * gtk_box_layout_new:
952 * @orientation: the orientation for the new layout
953 *
954 * Creates a new `GtkBoxLayout`.
955 *
956 * Returns: a new box layout
957 */
958GtkLayoutManager *
959gtk_box_layout_new (GtkOrientation orientation)
960{
961 return g_object_new (GTK_TYPE_BOX_LAYOUT,
962 first_property_name: "orientation", orientation,
963 NULL);
964}
965
966/**
967 * gtk_box_layout_set_homogeneous: (attributes org.gtk.Method.set_property=homogeneous)
968 * @box_layout: a `GtkBoxLayout`
969 * @homogeneous: %TRUE to set the box layout as homogeneous
970 *
971 * Sets whether the box layout will allocate the same
972 * size to all children.
973 */
974void
975gtk_box_layout_set_homogeneous (GtkBoxLayout *box_layout,
976 gboolean homogeneous)
977{
978 g_return_if_fail (GTK_IS_BOX_LAYOUT (box_layout));
979
980 homogeneous = !!homogeneous;
981 if (box_layout->homogeneous == homogeneous)
982 return;
983
984 box_layout->homogeneous = homogeneous;
985
986 gtk_layout_manager_layout_changed (manager: GTK_LAYOUT_MANAGER (ptr: box_layout));
987 g_object_notify_by_pspec (G_OBJECT (box_layout), pspec: box_layout_props[PROP_HOMOGENEOUS]);
988}
989
990/**
991 * gtk_box_layout_get_homogeneous: (attributes org.gtk.Method.get_property=homogeneous)
992 * @box_layout: a `GtkBoxLayout`
993 *
994 * Returns whether the layout is set to be homogeneous.
995 *
996 * Return: %TRUE if the layout is homogeneous
997 */
998gboolean
999gtk_box_layout_get_homogeneous (GtkBoxLayout *box_layout)
1000{
1001 g_return_val_if_fail (GTK_IS_BOX_LAYOUT (box_layout), FALSE);
1002
1003 return box_layout->homogeneous;
1004}
1005
1006/**
1007 * gtk_box_layout_set_spacing: (attributes org.gtk.Method.set_property=spacing)
1008 * @box_layout: a `GtkBoxLayout`
1009 * @spacing: the spacing to apply between children
1010 *
1011 * Sets how much spacing to put between children.
1012 */
1013void
1014gtk_box_layout_set_spacing (GtkBoxLayout *box_layout,
1015 guint spacing)
1016{
1017 g_return_if_fail (GTK_IS_BOX_LAYOUT (box_layout));
1018
1019 if (box_layout->spacing == spacing)
1020 return;
1021
1022 box_layout->spacing = spacing;
1023
1024 gtk_layout_manager_layout_changed (manager: GTK_LAYOUT_MANAGER (ptr: box_layout));
1025 g_object_notify_by_pspec (G_OBJECT (box_layout), pspec: box_layout_props[PROP_SPACING]);
1026}
1027
1028/**
1029 * gtk_box_layout_get_spacing: (attributes org.gtk.Method.get_property=spacing)
1030 * @box_layout: a `GtkBoxLayout`
1031 *
1032 * Returns the space that @box_layout puts between children.
1033 *
1034 * Returns: the spacing of the layout
1035 */
1036guint
1037gtk_box_layout_get_spacing (GtkBoxLayout *box_layout)
1038{
1039 g_return_val_if_fail (GTK_IS_BOX_LAYOUT (box_layout), 0);
1040
1041 return box_layout->spacing;
1042}
1043
1044/**
1045 * gtk_box_layout_set_baseline_position: (attributes org.gtk.Method.set_property=baseline-position)
1046 * @box_layout: a `GtkBoxLayout`
1047 * @position: a `GtkBaselinePosition`
1048 *
1049 * Sets the baseline position of a box layout.
1050 *
1051 * The baseline position affects only horizontal boxes with at least one
1052 * baseline aligned child. If there is more vertical space available than
1053 * requested, and the baseline is not allocated by the parent then the
1054 * given @position is used to allocate the baseline within the extra
1055 * space available.
1056 */
1057void
1058gtk_box_layout_set_baseline_position (GtkBoxLayout *box_layout,
1059 GtkBaselinePosition position)
1060{
1061 g_return_if_fail (GTK_IS_BOX_LAYOUT (box_layout));
1062
1063 if (box_layout->baseline_position != position)
1064 {
1065 box_layout->baseline_position = position;
1066
1067 g_object_notify_by_pspec (G_OBJECT (box_layout), pspec: box_layout_props[PROP_BASELINE_POSITION]);
1068
1069 gtk_layout_manager_layout_changed (manager: GTK_LAYOUT_MANAGER (ptr: box_layout));
1070 }
1071}
1072
1073/**
1074 * gtk_box_layout_get_baseline_position: (attributes org.gtk.Method.get_property=baseline-position)
1075 * @box_layout: a `GtkBoxLayout`
1076 *
1077 * Gets the value set by gtk_box_layout_set_baseline_position().
1078 *
1079 * Returns: the baseline position
1080 */
1081GtkBaselinePosition
1082gtk_box_layout_get_baseline_position (GtkBoxLayout *box_layout)
1083{
1084 g_return_val_if_fail (GTK_IS_BOX_LAYOUT (box_layout), GTK_BASELINE_POSITION_CENTER);
1085
1086 return box_layout->baseline_position;
1087}
1088

source code of gtk/gtk/gtkboxlayout.c