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 | |
51 | struct _GtkBoxLayout |
52 | { |
53 | GtkLayoutManager parent_instance; |
54 | |
55 | gboolean homogeneous; |
56 | guint spacing; |
57 | GtkOrientation orientation; |
58 | GtkBaselinePosition baseline_position; |
59 | }; |
60 | |
61 | G_DEFINE_TYPE_WITH_CODE (GtkBoxLayout, gtk_box_layout, GTK_TYPE_LAYOUT_MANAGER, |
62 | G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL)) |
63 | |
64 | enum { |
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 | |
75 | static GParamSpec *box_layout_props[N_PROPS]; |
76 | |
77 | static void |
78 | gtk_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 | |
98 | static void |
99 | gtk_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 | |
130 | static void |
131 | gtk_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 | |
162 | static void |
163 | count_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 | |
186 | static int |
187 | get_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 | |
203 | static void |
204 | gtk_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 | |
256 | static void |
257 | gtk_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 | |
297 | static int |
298 | distribute_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 | |
388 | static void |
389 | gtk_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 = 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 | |
610 | static void |
611 | gtk_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 | |
644 | static void |
645 | gtk_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 ; |
663 | int children_minimum_size = 0; |
664 | int size_given_to_child; |
665 | int = 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 | |
880 | static void |
881 | gtk_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 | |
941 | static void |
942 | gtk_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 | */ |
958 | GtkLayoutManager * |
959 | gtk_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 | */ |
974 | void |
975 | gtk_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 | */ |
998 | gboolean |
999 | gtk_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 | */ |
1013 | void |
1014 | gtk_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 | */ |
1036 | guint |
1037 | gtk_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 | */ |
1057 | void |
1058 | gtk_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 | */ |
1081 | GtkBaselinePosition |
1082 | gtk_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 | |