1/* gtkconstraintlayout.c: Layout manager using constraints
2 * Copyright 2019 GNOME Foundation
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * Author: Emmanuele Bassi
18 */
19
20/**
21 * GtkConstraintLayout:
22 *
23 * A layout manager using constraints to describe relations between widgets.
24 *
25 * `GtkConstraintLayout` is a layout manager that uses relations between
26 * widget attributes, expressed via [class@Gtk.Constraint] instances, to
27 * measure and allocate widgets.
28 *
29 * ### How do constraints work
30 *
31 * Constraints are objects defining the relationship between attributes
32 * of a widget; you can read the description of the [class@Gtk.Constraint]
33 * class to have a more in depth definition.
34 *
35 * By taking multiple constraints and applying them to the children of
36 * a widget using `GtkConstraintLayout`, it's possible to describe
37 * complex layout policies; each constraint applied to a child or to the parent
38 * widgets contributes to the full description of the layout, in terms of
39 * parameters for resolving the value of each attribute.
40 *
41 * It is important to note that a layout is defined by the totality of
42 * constraints; removing a child, or a constraint, from an existing layout
43 * without changing the remaining constraints may result in an unstable
44 * or unsolvable layout.
45 *
46 * Constraints have an implicit "reading order"; you should start describing
47 * each edge of each child, as well as their relationship with the parent
48 * container, from the top left (or top right, in RTL languages), horizontally
49 * first, and then vertically.
50 *
51 * A constraint-based layout with too few constraints can become "unstable",
52 * that is: have more than one solution. The behavior of an unstable layout
53 * is undefined.
54 *
55 * A constraint-based layout with conflicting constraints may be unsolvable,
56 * and lead to an unstable layout. You can use the [property@Gtk.Constraint:strength]
57 * property of [class@Gtk.Constraint] to "nudge" the layout towards a solution.
58 *
59 * ### GtkConstraintLayout as GtkBuildable
60 *
61 * `GtkConstraintLayout` implements the [iface@Gtk.Buildable] interface and
62 * has a custom "constraints" element which allows describing constraints in
63 * a [class@Gtk.Builder] UI file.
64 *
65 * An example of a UI definition fragment specifying a constraint:
66 *
67 * ```xml
68 * <object class="GtkConstraintLayout">
69 * <constraints>
70 * <constraint target="button" target-attribute="start"
71 * relation="eq"
72 * source="super" source-attribute="start"
73 * constant="12"
74 * strength="required" />
75 * <constraint target="button" target-attribute="width"
76 * relation="ge"
77 * constant="250"
78 * strength="strong" />
79 * </constraints>
80 * </object>
81 * ```
82 *
83 * The definition above will add two constraints to the GtkConstraintLayout:
84 *
85 * - a required constraint between the leading edge of "button" and
86 * the leading edge of the widget using the constraint layout, plus
87 * 12 pixels
88 * - a strong, constant constraint making the width of "button" greater
89 * than, or equal to 250 pixels
90 *
91 * The "target" and "target-attribute" attributes are required.
92 *
93 * The "source" and "source-attribute" attributes of the "constraint"
94 * element are optional; if they are not specified, the constraint is
95 * assumed to be a constant.
96 *
97 * The "relation" attribute is optional; if not specified, the constraint
98 * is assumed to be an equality.
99 *
100 * The "strength" attribute is optional; if not specified, the constraint
101 * is assumed to be required.
102 *
103 * The "source" and "target" attributes can be set to "super" to indicate
104 * that the constraint target is the widget using the GtkConstraintLayout.
105 *
106 * There can be "constant" and "multiplier" attributes.
107 *
108 * Additionally, the "constraints" element can also contain a description
109 * of the `GtkConstraintGuides` used by the layout:
110 *
111 * ```xml
112 * <constraints>
113 * <guide min-width="100" max-width="500" name="hspace"/>
114 * <guide min-height="64" nat-height="128" name="vspace" strength="strong"/>
115 * </constraints>
116 * ```
117 *
118 * The "guide" element has the following optional attributes:
119 *
120 * - "min-width", "nat-width", and "max-width", describe the minimum,
121 * natural, and maximum width of the guide, respectively
122 * - "min-height", "nat-height", and "max-height", describe the minimum,
123 * natural, and maximum height of the guide, respectively
124 * - "strength" describes the strength of the constraint on the natural
125 * size of the guide; if not specified, the constraint is assumed to
126 * have a medium strength
127 * - "name" describes a name for the guide, useful when debugging
128 *
129 * ### Using the Visual Format Language
130 *
131 * Complex constraints can be described using a compact syntax called VFL,
132 * or *Visual Format Language*.
133 *
134 * The Visual Format Language describes all the constraints on a row or
135 * column, typically starting from the leading edge towards the trailing
136 * one. Each element of the layout is composed by "views", which identify
137 * a [iface@Gtk.ConstraintTarget].
138 *
139 * For instance:
140 *
141 * ```
142 * [button]-[textField]
143 * ```
144 *
145 * Describes a constraint that binds the trailing edge of "button" to the
146 * leading edge of "textField", leaving a default space between the two.
147 *
148 * Using VFL is also possible to specify predicates that describe constraints
149 * on attributes like width and height:
150 *
151 * ```
152 * // Width must be greater than, or equal to 50
153 * [button(>=50)]
154 *
155 * // Width of button1 must be equal to width of button2
156 * [button1(==button2)]
157 * ```
158 *
159 * The default orientation for a VFL description is horizontal, unless
160 * otherwise specified:
161 *
162 * ```
163 * // horizontal orientation, default attribute: width
164 * H:[button(>=150)]
165 *
166 * // vertical orientation, default attribute: height
167 * V:[button1(==button2)]
168 * ```
169 *
170 * It's also possible to specify multiple predicates, as well as their
171 * strength:
172 *
173 * ```
174 * // minimum width of button must be 150
175 * // natural width of button can be 250
176 * [button(>=150@required, ==250@medium)]
177 * ```
178 *
179 * Finally, it's also possible to use simple arithmetic operators:
180 *
181 * ```
182 * // width of button1 must be equal to width of button2
183 * // divided by 2 plus 12
184 * [button1(button2 / 2 + 12)]
185 * ```
186 */
187
188/**
189 * GtkConstraintLayoutChild:
190 *
191 * `GtkLayoutChild` subclass for children in a `GtkConstraintLayout`.
192 */
193
194#include "config.h"
195
196#include "gtkconstraintlayout.h"
197#include "gtkconstraintlayoutprivate.h"
198
199#include "gtkconstraintprivate.h"
200#include "gtkconstraintexpressionprivate.h"
201#include "gtkconstraintguideprivate.h"
202#include "gtkconstraintsolverprivate.h"
203#include "gtkconstraintvflparserprivate.h"
204
205#include "gtkbuildable.h"
206#include "gtkbuilderprivate.h"
207#include "gtkdebug.h"
208#include "gtklayoutchild.h"
209#include "gtkintl.h"
210#include "gtkprivate.h"
211#include "gtksizerequest.h"
212#include "gtkwidgetprivate.h"
213
214#include <string.h>
215#include <errno.h>
216
217enum {
218 MIN_WIDTH,
219 MIN_HEIGHT,
220 NAT_WIDTH,
221 NAT_HEIGHT,
222 LAST_VALUE
223};
224
225struct _GtkConstraintLayoutChild
226{
227 GtkLayoutChild parent_instance;
228
229 int values[LAST_VALUE];
230 GtkConstraintRef *constraints[LAST_VALUE];
231
232 /* HashTable<static string, Variable>; a hash table of variables,
233 * one for each attribute; we use these to query and suggest the
234 * values for the solver. The string is static and does not need
235 * to be freed.
236 */
237 GHashTable *bound_attributes;
238};
239
240struct _GtkConstraintLayout
241{
242 GtkLayoutManager parent_instance;
243
244 /* A pointer to the GtkConstraintSolver used by the layout manager;
245 * we acquire one when the layout manager gets rooted, and release
246 * it when it gets unrooted.
247 */
248 GtkConstraintSolver *solver;
249
250 /* HashTable<static string, Variable>; a hash table of variables,
251 * one for each attribute; we use these to query and suggest the
252 * values for the solver. The string is static and does not need
253 * to be freed.
254 */
255 GHashTable *bound_attributes;
256
257 /* HashSet<GtkConstraint>; the set of constraints on the
258 * parent widget, using the public API objects.
259 */
260 GHashTable *constraints;
261
262 /* HashSet<GtkConstraintGuide> */
263 GHashTable *guides;
264
265 GListStore *constraints_observer;
266 GListStore *guides_observer;
267};
268
269G_DEFINE_TYPE (GtkConstraintLayoutChild, gtk_constraint_layout_child, GTK_TYPE_LAYOUT_CHILD)
270
271GtkConstraintSolver *
272gtk_constraint_layout_get_solver (GtkConstraintLayout *self)
273{
274 GtkWidget *widget;
275 GtkRoot *root;
276
277 if (self->solver != NULL)
278 return self->solver;
279
280 widget = gtk_layout_manager_get_widget (manager: GTK_LAYOUT_MANAGER (ptr: self));
281 if (widget == NULL)
282 return NULL;
283
284 root = gtk_widget_get_root (widget);
285 if (root == NULL)
286 return NULL;
287
288 self->solver = gtk_root_get_constraint_solver (self: root);
289
290 return self->solver;
291}
292
293static const char * const attribute_names[] = {
294 [GTK_CONSTRAINT_ATTRIBUTE_NONE] = "none",
295 [GTK_CONSTRAINT_ATTRIBUTE_LEFT] = "left",
296 [GTK_CONSTRAINT_ATTRIBUTE_RIGHT] = "right",
297 [GTK_CONSTRAINT_ATTRIBUTE_TOP] = "top",
298 [GTK_CONSTRAINT_ATTRIBUTE_BOTTOM] = "bottom",
299 [GTK_CONSTRAINT_ATTRIBUTE_START] = "start",
300 [GTK_CONSTRAINT_ATTRIBUTE_END] = "end",
301 [GTK_CONSTRAINT_ATTRIBUTE_WIDTH] = "width",
302 [GTK_CONSTRAINT_ATTRIBUTE_HEIGHT] = "height",
303 [GTK_CONSTRAINT_ATTRIBUTE_CENTER_X] = "center-x",
304 [GTK_CONSTRAINT_ATTRIBUTE_CENTER_Y] = "center-y",
305 [GTK_CONSTRAINT_ATTRIBUTE_BASELINE] = "baseline",
306};
307
308G_GNUC_PURE
309static const char *
310get_attribute_name (GtkConstraintAttribute attr)
311{
312 return attribute_names[attr];
313}
314
315static GtkConstraintAttribute
316resolve_direction (GtkConstraintAttribute attr,
317 GtkWidget *widget)
318{
319 GtkTextDirection text_dir;
320
321 /* Resolve the start/end attributes depending on the layout's text direction */
322
323 if (widget)
324 text_dir = gtk_widget_get_direction (widget);
325 else
326 text_dir = GTK_TEXT_DIR_LTR;
327
328 if (attr == GTK_CONSTRAINT_ATTRIBUTE_START)
329 {
330 if (text_dir == GTK_TEXT_DIR_RTL)
331 attr = GTK_CONSTRAINT_ATTRIBUTE_RIGHT;
332 else
333 attr = GTK_CONSTRAINT_ATTRIBUTE_LEFT;
334 }
335 else if (attr == GTK_CONSTRAINT_ATTRIBUTE_END)
336 {
337 if (text_dir == GTK_TEXT_DIR_RTL)
338 attr = GTK_CONSTRAINT_ATTRIBUTE_LEFT;
339 else
340 attr = GTK_CONSTRAINT_ATTRIBUTE_RIGHT;
341 }
342
343 return attr;
344}
345
346GtkConstraintVariable *
347gtk_constraint_layout_get_attribute (GtkConstraintLayout *layout,
348 GtkConstraintAttribute attr,
349 const char *prefix,
350 GtkWidget *widget,
351 GHashTable *bound_attributes)
352{
353 const char *attr_name;
354 GtkConstraintVariable *res;
355 GtkConstraintSolver *solver = layout->solver;
356
357 attr = resolve_direction (attr, widget);
358
359 attr_name = get_attribute_name (attr);
360 res = g_hash_table_lookup (hash_table: bound_attributes, key: attr_name);
361 if (res != NULL)
362 return res;
363
364 res = gtk_constraint_solver_create_variable (solver, prefix, name: attr_name, value: 0.0);
365 g_hash_table_insert (hash_table: bound_attributes, key: (gpointer) attr_name, value: res);
366
367 /* Some attributes are really constraints computed from other
368 * attributes, to avoid creating additional constraints from
369 * the user's perspective
370 */
371 switch (attr)
372 {
373 /* right = left + width */
374 case GTK_CONSTRAINT_ATTRIBUTE_RIGHT:
375 {
376 GtkConstraintExpressionBuilder builder;
377 GtkConstraintVariable *left, *width;
378 GtkConstraintExpression *expr;
379
380 left = gtk_constraint_layout_get_attribute (layout, attr: GTK_CONSTRAINT_ATTRIBUTE_LEFT, prefix, widget, bound_attributes);
381 width = gtk_constraint_layout_get_attribute (layout, attr: GTK_CONSTRAINT_ATTRIBUTE_WIDTH, prefix, widget, bound_attributes);
382
383 gtk_constraint_expression_builder_init (builder: &builder, solver);
384 gtk_constraint_expression_builder_term (builder: &builder, term: left);
385 gtk_constraint_expression_builder_plus (builder: &builder);
386 gtk_constraint_expression_builder_term (builder: &builder, term: width);
387 expr = gtk_constraint_expression_builder_finish (builder: &builder);
388
389 gtk_constraint_solver_add_constraint (solver,
390 variable: res, relation: GTK_CONSTRAINT_RELATION_EQ, expression: expr,
391 strength: GTK_CONSTRAINT_STRENGTH_REQUIRED);
392 }
393 break;
394
395 /* bottom = top + height */
396 case GTK_CONSTRAINT_ATTRIBUTE_BOTTOM:
397 {
398 GtkConstraintExpressionBuilder builder;
399 GtkConstraintVariable *top, *height;
400 GtkConstraintExpression *expr;
401
402 top = gtk_constraint_layout_get_attribute (layout, attr: GTK_CONSTRAINT_ATTRIBUTE_TOP, prefix, widget, bound_attributes);
403 height = gtk_constraint_layout_get_attribute (layout, attr: GTK_CONSTRAINT_ATTRIBUTE_HEIGHT, prefix, widget, bound_attributes);
404
405 gtk_constraint_expression_builder_init (builder: &builder, solver);
406 gtk_constraint_expression_builder_term (builder: &builder, term: top);
407 gtk_constraint_expression_builder_plus (builder: &builder);
408 gtk_constraint_expression_builder_term (builder: &builder, term: height);
409 expr = gtk_constraint_expression_builder_finish (builder: &builder);
410
411 gtk_constraint_solver_add_constraint (solver,
412 variable: res, relation: GTK_CONSTRAINT_RELATION_EQ, expression: expr,
413 strength: GTK_CONSTRAINT_STRENGTH_REQUIRED);
414 }
415 break;
416
417 /* centerX = (width / 2.0) + left*/
418 case GTK_CONSTRAINT_ATTRIBUTE_CENTER_X:
419 {
420 GtkConstraintExpressionBuilder builder;
421 GtkConstraintVariable *left, *width;
422 GtkConstraintExpression *expr;
423
424 left = gtk_constraint_layout_get_attribute (layout, attr: GTK_CONSTRAINT_ATTRIBUTE_LEFT, prefix, widget, bound_attributes);
425 width = gtk_constraint_layout_get_attribute (layout, attr: GTK_CONSTRAINT_ATTRIBUTE_WIDTH, prefix, widget, bound_attributes);
426
427 gtk_constraint_expression_builder_init (builder: &builder, solver);
428 gtk_constraint_expression_builder_term (builder: &builder, term: width);
429 gtk_constraint_expression_builder_divide_by (builder: &builder);
430 gtk_constraint_expression_builder_constant (builder: &builder, value: 2.0);
431 gtk_constraint_expression_builder_plus (builder: &builder);
432 gtk_constraint_expression_builder_term (builder: &builder, term: left);
433 expr = gtk_constraint_expression_builder_finish (builder: &builder);
434
435 gtk_constraint_solver_add_constraint (solver,
436 variable: res, relation: GTK_CONSTRAINT_RELATION_EQ, expression: expr,
437 strength: GTK_CONSTRAINT_STRENGTH_REQUIRED);
438 }
439 break;
440
441 /* centerY = (height / 2.0) + top */
442 case GTK_CONSTRAINT_ATTRIBUTE_CENTER_Y:
443 {
444 GtkConstraintExpressionBuilder builder;
445 GtkConstraintVariable *top, *height;
446 GtkConstraintExpression *expr;
447
448 top = gtk_constraint_layout_get_attribute (layout, attr: GTK_CONSTRAINT_ATTRIBUTE_TOP, prefix, widget, bound_attributes);
449 height = gtk_constraint_layout_get_attribute (layout, attr: GTK_CONSTRAINT_ATTRIBUTE_HEIGHT, prefix, widget, bound_attributes);
450
451 gtk_constraint_expression_builder_init (builder: &builder, solver);
452 gtk_constraint_expression_builder_term (builder: &builder, term: height);
453 gtk_constraint_expression_builder_divide_by (builder: &builder);
454 gtk_constraint_expression_builder_constant (builder: &builder, value: 2.0);
455 gtk_constraint_expression_builder_plus (builder: &builder);
456 gtk_constraint_expression_builder_term (builder: &builder, term: top);
457 expr = gtk_constraint_expression_builder_finish (builder: &builder);
458
459 gtk_constraint_solver_add_constraint (solver,
460 variable: res, relation: GTK_CONSTRAINT_RELATION_EQ, expression: expr,
461 strength: GTK_CONSTRAINT_STRENGTH_REQUIRED);
462 }
463 break;
464
465 /* We do not allow negative sizes */
466 case GTK_CONSTRAINT_ATTRIBUTE_WIDTH:
467 case GTK_CONSTRAINT_ATTRIBUTE_HEIGHT:
468 {
469 GtkConstraintExpression *expr;
470
471 expr = gtk_constraint_expression_new (constant: 0.0);
472 gtk_constraint_solver_add_constraint (solver,
473 variable: res, relation: GTK_CONSTRAINT_RELATION_GE, expression: expr,
474 strength: GTK_CONSTRAINT_STRENGTH_REQUIRED);
475 }
476 break;
477
478 /* These are "pure" attributes */
479 case GTK_CONSTRAINT_ATTRIBUTE_NONE:
480 case GTK_CONSTRAINT_ATTRIBUTE_LEFT:
481 case GTK_CONSTRAINT_ATTRIBUTE_TOP:
482 case GTK_CONSTRAINT_ATTRIBUTE_BASELINE:
483 break;
484
485 /* These attributes must have been resolved to their real names */
486 case GTK_CONSTRAINT_ATTRIBUTE_START:
487 case GTK_CONSTRAINT_ATTRIBUTE_END:
488 g_assert_not_reached ();
489 break;
490
491 default:
492 break;
493 }
494
495 return res;
496}
497
498static GtkConstraintVariable *
499get_child_attribute (GtkConstraintLayout *layout,
500 GtkWidget *widget,
501 GtkConstraintAttribute attr)
502{
503 GtkConstraintLayoutChild *child_info;
504 const char *prefix = gtk_widget_get_name (widget);
505
506 child_info = GTK_CONSTRAINT_LAYOUT_CHILD (ptr: gtk_layout_manager_get_layout_child (manager: GTK_LAYOUT_MANAGER (ptr: layout), child: widget));
507
508 return gtk_constraint_layout_get_attribute (layout, attr, prefix, widget, bound_attributes: child_info->bound_attributes);
509}
510
511static void
512gtk_constraint_layout_child_finalize (GObject *gobject)
513{
514 GtkConstraintLayoutChild *self = GTK_CONSTRAINT_LAYOUT_CHILD (ptr: gobject);
515
516 g_clear_pointer (&self->bound_attributes, g_hash_table_unref);
517
518 G_OBJECT_CLASS (gtk_constraint_layout_child_parent_class)->finalize (gobject);
519}
520
521static void
522gtk_constraint_layout_child_class_init (GtkConstraintLayoutChildClass *klass)
523{
524 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
525
526 gobject_class->finalize = gtk_constraint_layout_child_finalize;
527}
528
529static void
530gtk_constraint_layout_child_init (GtkConstraintLayoutChild *self)
531{
532 self->bound_attributes =
533 g_hash_table_new_full (hash_func: g_str_hash, key_equal_func: g_str_equal,
534 NULL,
535 value_destroy_func: (GDestroyNotify) gtk_constraint_variable_unref);
536}
537
538static void gtk_buildable_interface_init (GtkBuildableIface *iface);
539
540G_DEFINE_TYPE_WITH_CODE (GtkConstraintLayout, gtk_constraint_layout, GTK_TYPE_LAYOUT_MANAGER,
541 G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, gtk_buildable_interface_init))
542
543static void
544gtk_constraint_layout_finalize (GObject *gobject)
545{
546 GtkConstraintLayout *self = GTK_CONSTRAINT_LAYOUT (ptr: gobject);
547
548 if (self->constraints_observer)
549 {
550 g_list_store_remove_all (store: self->constraints_observer);
551 g_object_remove_weak_pointer (object: (GObject *)self->constraints_observer,
552 weak_pointer_location: (gpointer *)&self->constraints_observer);
553 }
554 if (self->guides_observer)
555 {
556 g_list_store_remove_all (store: self->guides_observer);
557 g_object_remove_weak_pointer (object: (GObject *)self->guides_observer,
558 weak_pointer_location: (gpointer *)&self->guides_observer);
559 }
560
561 g_clear_pointer (&self->bound_attributes, g_hash_table_unref);
562 g_clear_pointer (&self->constraints, g_hash_table_unref);
563 g_clear_pointer (&self->guides, g_hash_table_unref);
564
565 G_OBJECT_CLASS (gtk_constraint_layout_parent_class)->finalize (gobject);
566}
567
568static GtkConstraintVariable *
569get_layout_attribute (GtkConstraintLayout *self,
570 GtkWidget *widget,
571 GtkConstraintAttribute attr)
572{
573 GtkTextDirection text_dir;
574 const char *attr_name;
575 GtkConstraintVariable *res;
576
577 /* Resolve the start/end attributes depending on the layout's text direction */
578 if (attr == GTK_CONSTRAINT_ATTRIBUTE_START)
579 {
580 text_dir = gtk_widget_get_direction (widget);
581 if (text_dir == GTK_TEXT_DIR_RTL)
582 attr = GTK_CONSTRAINT_ATTRIBUTE_RIGHT;
583 else
584 attr = GTK_CONSTRAINT_ATTRIBUTE_LEFT;
585 }
586 else if (attr == GTK_CONSTRAINT_ATTRIBUTE_END)
587 {
588 text_dir = gtk_widget_get_direction (widget);
589 if (text_dir == GTK_TEXT_DIR_RTL)
590 attr = GTK_CONSTRAINT_ATTRIBUTE_LEFT;
591 else
592 attr = GTK_CONSTRAINT_ATTRIBUTE_RIGHT;
593 }
594
595 attr_name = get_attribute_name (attr);
596 res = g_hash_table_lookup (hash_table: self->bound_attributes, key: attr_name);
597 if (res != NULL)
598 return res;
599
600 res = gtk_constraint_solver_create_variable (solver: self->solver, prefix: "super", name: attr_name, value: 0.0);
601 g_hash_table_insert (hash_table: self->bound_attributes, key: (gpointer) attr_name, value: res);
602
603 /* Some attributes are really constraints computed from other
604 * attributes, to avoid creating additional constraints from
605 * the user's perspective
606 */
607 switch (attr)
608 {
609 /* right = left + width */
610 case GTK_CONSTRAINT_ATTRIBUTE_RIGHT:
611 {
612 GtkConstraintExpressionBuilder builder;
613 GtkConstraintVariable *left, *width;
614 GtkConstraintExpression *expr;
615
616 left = get_layout_attribute (self, widget, attr: GTK_CONSTRAINT_ATTRIBUTE_LEFT);
617 width = get_layout_attribute (self, widget, attr: GTK_CONSTRAINT_ATTRIBUTE_WIDTH);
618
619 gtk_constraint_expression_builder_init (builder: &builder, solver: self->solver);
620 gtk_constraint_expression_builder_term (builder: &builder, term: left);
621 gtk_constraint_expression_builder_plus (builder: &builder);
622 gtk_constraint_expression_builder_term (builder: &builder, term: width);
623 expr = gtk_constraint_expression_builder_finish (builder: &builder);
624
625 gtk_constraint_solver_add_constraint (solver: self->solver,
626 variable: res, relation: GTK_CONSTRAINT_RELATION_EQ, expression: expr,
627 strength: GTK_CONSTRAINT_STRENGTH_REQUIRED);
628 }
629 break;
630
631 /* bottom = top + height */
632 case GTK_CONSTRAINT_ATTRIBUTE_BOTTOM:
633 {
634 GtkConstraintExpressionBuilder builder;
635 GtkConstraintVariable *top, *height;
636 GtkConstraintExpression *expr;
637
638 top = get_layout_attribute (self, widget, attr: GTK_CONSTRAINT_ATTRIBUTE_TOP);
639 height = get_layout_attribute (self, widget, attr: GTK_CONSTRAINT_ATTRIBUTE_HEIGHT);
640
641 gtk_constraint_expression_builder_init (builder: &builder, solver: self->solver);
642 gtk_constraint_expression_builder_term (builder: &builder, term: top);
643 gtk_constraint_expression_builder_plus (builder: &builder);
644 gtk_constraint_expression_builder_term (builder: &builder, term: height);
645 expr = gtk_constraint_expression_builder_finish (builder: &builder);
646
647 gtk_constraint_solver_add_constraint (solver: self->solver,
648 variable: res, relation: GTK_CONSTRAINT_RELATION_EQ, expression: expr,
649 strength: GTK_CONSTRAINT_STRENGTH_REQUIRED);
650 }
651 break;
652
653 /* centerX = left + (width / 2.0) */
654 case GTK_CONSTRAINT_ATTRIBUTE_CENTER_X:
655 {
656 GtkConstraintExpressionBuilder builder;
657 GtkConstraintVariable *left, *width;
658 GtkConstraintExpression *expr;
659
660 left = get_layout_attribute (self, widget, attr: GTK_CONSTRAINT_ATTRIBUTE_LEFT);
661 width = get_layout_attribute (self, widget, attr: GTK_CONSTRAINT_ATTRIBUTE_WIDTH);
662
663 gtk_constraint_expression_builder_init (builder: &builder, solver: self->solver);
664 gtk_constraint_expression_builder_term (builder: &builder, term: width);
665 gtk_constraint_expression_builder_divide_by (builder: &builder);
666 gtk_constraint_expression_builder_constant (builder: &builder, value: 2.0);
667 gtk_constraint_expression_builder_plus (builder: &builder);
668 gtk_constraint_expression_builder_term (builder: &builder, term: left);
669 expr = gtk_constraint_expression_builder_finish (builder: &builder);
670
671 gtk_constraint_solver_add_constraint (solver: self->solver,
672 variable: res, relation: GTK_CONSTRAINT_RELATION_EQ, expression: expr,
673 strength: GTK_CONSTRAINT_STRENGTH_REQUIRED);
674 }
675 break;
676
677 /* centerY = top + (height / 2.0) */
678 case GTK_CONSTRAINT_ATTRIBUTE_CENTER_Y:
679 {
680 GtkConstraintExpressionBuilder builder;
681 GtkConstraintVariable *top, *height;
682 GtkConstraintExpression *expr;
683
684 top = get_layout_attribute (self, widget, attr: GTK_CONSTRAINT_ATTRIBUTE_TOP);
685 height = get_layout_attribute (self, widget, attr: GTK_CONSTRAINT_ATTRIBUTE_HEIGHT);
686
687 gtk_constraint_expression_builder_init (builder: &builder, solver: self->solver);
688 gtk_constraint_expression_builder_term (builder: &builder, term: height);
689 gtk_constraint_expression_builder_divide_by (builder: &builder);
690 gtk_constraint_expression_builder_constant (builder: &builder, value: 2.0);
691 gtk_constraint_expression_builder_plus (builder: &builder);
692 gtk_constraint_expression_builder_term (builder: &builder, term: top);
693 expr = gtk_constraint_expression_builder_finish (builder: &builder);
694
695 gtk_constraint_solver_add_constraint (solver: self->solver,
696 variable: res, relation: GTK_CONSTRAINT_RELATION_EQ, expression: expr,
697 strength: GTK_CONSTRAINT_STRENGTH_REQUIRED);
698 }
699 break;
700
701 /* We do not allow negative sizes */
702 case GTK_CONSTRAINT_ATTRIBUTE_WIDTH:
703 case GTK_CONSTRAINT_ATTRIBUTE_HEIGHT:
704 {
705 GtkConstraintExpression *expr;
706
707 expr = gtk_constraint_expression_new (constant: 0.0);
708 gtk_constraint_solver_add_constraint (solver: self->solver,
709 variable: res, relation: GTK_CONSTRAINT_RELATION_GE, expression: expr,
710 strength: GTK_CONSTRAINT_STRENGTH_REQUIRED);
711 }
712 break;
713
714 /* These are "pure" attributes */
715 case GTK_CONSTRAINT_ATTRIBUTE_NONE:
716 case GTK_CONSTRAINT_ATTRIBUTE_LEFT:
717 case GTK_CONSTRAINT_ATTRIBUTE_TOP:
718 case GTK_CONSTRAINT_ATTRIBUTE_BASELINE:
719 break;
720
721 /* These attributes must have been resolved to their real names */
722 case GTK_CONSTRAINT_ATTRIBUTE_START:
723 case GTK_CONSTRAINT_ATTRIBUTE_END:
724 g_assert_not_reached ();
725 break;
726
727 default:
728 break;
729 }
730
731 return res;
732}
733
734/*< private >
735 * layout_add_constraint:
736 * @self: a `GtkConstraintLayout`
737 * @constraint: a [class@Gtk.Constraint]
738 *
739 * Turns a `GtkConstraint` into a `GtkConstraintRef` inside the
740 * constraint solver associated to @self.
741 *
742 * If @self does not have a `GtkConstraintSolver`, because it
743 * has not been rooted yet, we just store the @constraint instance,
744 * and we're going to call this function when the layout manager
745 * gets rooted.
746 */
747static void
748layout_add_constraint (GtkConstraintLayout *self,
749 GtkConstraint *constraint)
750{
751 GtkConstraintVariable *target_attr, *source_attr;
752 GtkConstraintExpressionBuilder builder;
753 GtkConstraintExpression *expr;
754 GtkConstraintSolver *solver;
755 GtkConstraintAttribute attr;
756 GtkConstraintTarget *target, *source;
757 GtkWidget *layout_widget;
758
759 if (gtk_constraint_is_attached (constraint))
760 return;
761
762 /* Once we pass the preconditions, we check if we can turn a GtkConstraint
763 * into a GtkConstraintRef; if we can't, we keep a reference to the
764 * constraint object and try later on
765 */
766 layout_widget = gtk_layout_manager_get_widget (manager: GTK_LAYOUT_MANAGER (ptr: self));
767 if (layout_widget == NULL)
768 return;
769
770 solver = gtk_constraint_layout_get_solver (self);
771 if (solver == NULL)
772 return;
773
774 attr = gtk_constraint_get_target_attribute (constraint);
775 target = gtk_constraint_get_target (constraint);
776 if (target == NULL || target == GTK_CONSTRAINT_TARGET (ptr: layout_widget))
777 {
778 /* A NULL target widget is assumed to be referring to the layout itself */
779 target_attr = get_layout_attribute (self, widget: layout_widget, attr);
780 }
781 else if (GTK_IS_WIDGET (target) &&
782 gtk_widget_get_parent (GTK_WIDGET (target)) == layout_widget)
783 {
784 target_attr = get_child_attribute (layout: self, GTK_WIDGET (target), attr);
785 }
786 else if (GTK_IS_CONSTRAINT_GUIDE (ptr: target))
787 {
788 GtkConstraintGuide *guide;
789
790 guide = (GtkConstraintGuide*)g_hash_table_lookup (hash_table: self->guides, key: target);
791 target_attr = gtk_constraint_guide_get_attribute (guide, attr);
792 }
793 else
794 {
795 g_critical ("Unknown target widget '%p'", target);
796 target_attr = NULL;
797 }
798
799 if (target_attr == NULL)
800 return;
801
802 attr = gtk_constraint_get_source_attribute (constraint);
803 source = gtk_constraint_get_source (constraint);
804
805 /* The constraint is a constant */
806 if (attr == GTK_CONSTRAINT_ATTRIBUTE_NONE)
807 {
808 source_attr = NULL;
809 }
810 else
811 {
812 if (source == NULL || source == GTK_CONSTRAINT_TARGET (ptr: layout_widget))
813 {
814 source_attr = get_layout_attribute (self, widget: layout_widget, attr);
815 }
816 else if (GTK_IS_WIDGET (source) &&
817 gtk_widget_get_parent (GTK_WIDGET (source)) == layout_widget)
818 {
819 source_attr = get_child_attribute (layout: self, GTK_WIDGET (source), attr);
820 }
821 else if (GTK_IS_CONSTRAINT_GUIDE (ptr: source))
822 {
823 GtkConstraintGuide *guide;
824
825 guide = (GtkConstraintGuide*)g_hash_table_lookup (hash_table: self->guides, key: source);
826 source_attr = gtk_constraint_guide_get_attribute (guide, attr);
827 }
828 else
829 {
830 g_critical ("Unknown source widget '%p'", source);
831 source_attr = NULL;
832 return;
833 }
834 }
835
836 /* Build the expression */
837 gtk_constraint_expression_builder_init (builder: &builder, solver: self->solver);
838
839 if (source_attr != NULL)
840 {
841 gtk_constraint_expression_builder_term (builder: &builder, term: source_attr);
842 gtk_constraint_expression_builder_multiply_by (builder: &builder);
843 gtk_constraint_expression_builder_constant (builder: &builder, value: gtk_constraint_get_multiplier (constraint));
844 gtk_constraint_expression_builder_plus (builder: &builder);
845 }
846
847 gtk_constraint_expression_builder_constant (builder: &builder, value: gtk_constraint_get_constant (constraint));
848 expr = gtk_constraint_expression_builder_finish (builder: &builder);
849
850 constraint->solver = solver;
851 constraint->constraint_ref =
852 gtk_constraint_solver_add_constraint (solver: self->solver,
853 variable: target_attr,
854 relation: gtk_constraint_get_relation (constraint),
855 expression: expr,
856 strength: gtk_constraint_get_strength (constraint));
857}
858
859static void
860update_child_constraint (GtkConstraintLayout *self,
861 GtkConstraintLayoutChild *child_info,
862 GtkWidget *child,
863 int index,
864 int value)
865{
866
867 GtkConstraintVariable *var;
868 int attr[LAST_VALUE] = {
869 GTK_CONSTRAINT_ATTRIBUTE_WIDTH,
870 GTK_CONSTRAINT_ATTRIBUTE_HEIGHT,
871 GTK_CONSTRAINT_ATTRIBUTE_WIDTH,
872 GTK_CONSTRAINT_ATTRIBUTE_HEIGHT
873 };
874 int relation[LAST_VALUE] = {
875 GTK_CONSTRAINT_RELATION_GE,
876 GTK_CONSTRAINT_RELATION_GE,
877 GTK_CONSTRAINT_RELATION_EQ,
878 GTK_CONSTRAINT_RELATION_EQ
879 };
880
881 if (child_info->values[index] != value)
882 {
883 child_info->values[index] = value;
884
885 if (child_info->constraints[index])
886 gtk_constraint_solver_remove_constraint (solver: self->solver,
887 reference: child_info->constraints[index]);
888
889 var = get_child_attribute (layout: self, widget: child, attr: attr[index]);
890
891 if (relation[index] == GTK_CONSTRAINT_RELATION_EQ)
892 {
893 gtk_constraint_variable_set_value (variable: var, value);
894 child_info->constraints[index] =
895 gtk_constraint_solver_add_stay_variable (solver: self->solver,
896 variable: var,
897 strength: GTK_CONSTRAINT_STRENGTH_MEDIUM);
898 }
899 else
900 {
901 child_info->constraints[index] =
902 gtk_constraint_solver_add_constraint (solver: self->solver,
903 variable: var,
904 relation: relation[index],
905 expression: gtk_constraint_expression_new (constant: value),
906 strength: GTK_CONSTRAINT_STRENGTH_REQUIRED);
907 }
908 }
909}
910
911static void
912gtk_constraint_layout_measure (GtkLayoutManager *manager,
913 GtkWidget *widget,
914 GtkOrientation orientation,
915 int for_size,
916 int *minimum,
917 int *natural,
918 int *minimum_baseline,
919 int *natural_baseline)
920{
921 GtkConstraintLayout *self = GTK_CONSTRAINT_LAYOUT (ptr: manager);
922 GtkConstraintVariable *size, *opposite_size;
923 GtkConstraintSolver *solver;
924 GtkWidget *child;
925 int min_value;
926 int nat_value;
927
928 solver = gtk_constraint_layout_get_solver (self);
929 if (solver == NULL)
930 return;
931
932 gtk_constraint_solver_freeze (solver);
933
934 /* We measure each child in the layout and impose restrictions on the
935 * minimum and natural size, so we can solve the size of the overall
936 * layout later on
937 */
938 for (child = _gtk_widget_get_first_child (widget);
939 child != NULL;
940 child = _gtk_widget_get_next_sibling (widget: child))
941 {
942 GtkConstraintLayoutChild *info;
943 GtkRequisition min_req, nat_req;
944
945 if (!gtk_widget_should_layout (widget: child))
946 continue;
947
948 gtk_widget_get_preferred_size (widget: child, minimum_size: &min_req, natural_size: &nat_req);
949
950 info = GTK_CONSTRAINT_LAYOUT_CHILD (ptr: gtk_layout_manager_get_layout_child (manager, child));
951
952 update_child_constraint (self, child_info: info, child, index: MIN_WIDTH, value: min_req.width);
953 update_child_constraint (self, child_info: info, child, index: MIN_HEIGHT, value: min_req.height);
954 update_child_constraint (self, child_info: info, child, index: NAT_WIDTH, value: nat_req.width);
955 update_child_constraint (self, child_info: info, child, index: NAT_HEIGHT, value: nat_req.height);
956 }
957
958 gtk_constraint_solver_thaw (solver);
959
960 switch (orientation)
961 {
962 case GTK_ORIENTATION_HORIZONTAL:
963 size = get_layout_attribute (self, widget, attr: GTK_CONSTRAINT_ATTRIBUTE_WIDTH);
964 opposite_size = get_layout_attribute (self, widget, attr: GTK_CONSTRAINT_ATTRIBUTE_HEIGHT);
965 break;
966
967 case GTK_ORIENTATION_VERTICAL:
968 size = get_layout_attribute (self, widget, attr: GTK_CONSTRAINT_ATTRIBUTE_HEIGHT);
969 opposite_size = get_layout_attribute (self, widget, attr: GTK_CONSTRAINT_ATTRIBUTE_WIDTH);
970 break;
971
972 default:
973 g_assert_not_reached ();
974 }
975
976 g_assert (size != NULL && opposite_size != NULL);
977
978 nat_value = gtk_constraint_variable_get_value (variable: size);
979
980 /* We impose a temporary value on the size and opposite size of the
981 * layout, with a low weight to let the solver settle towards the
982 * natural state of the system. Once we get the value out, we can
983 * remove these constraints
984 */
985 gtk_constraint_solver_add_edit_variable (solver, variable: size, strength: GTK_CONSTRAINT_STRENGTH_STRONG * 2);
986 if (for_size > 0)
987 gtk_constraint_solver_add_edit_variable (solver, variable: opposite_size, strength: GTK_CONSTRAINT_STRENGTH_STRONG * 2);
988 gtk_constraint_solver_begin_edit (solver);
989 gtk_constraint_solver_suggest_value (solver, variable: size, value: 0.0);
990 if (for_size > 0)
991 gtk_constraint_solver_suggest_value (solver, variable: opposite_size, value: for_size);
992 gtk_constraint_solver_resolve (solver);
993
994 min_value = gtk_constraint_variable_get_value (variable: size);
995
996 gtk_constraint_solver_remove_edit_variable (solver, variable: size);
997 if (for_size > 0)
998 gtk_constraint_solver_remove_edit_variable (solver, variable: opposite_size);
999 gtk_constraint_solver_end_edit (solver);
1000
1001 GTK_NOTE (LAYOUT,
1002 g_print ("layout %p %s size: min %d nat %d (for opposite size: %d)\n",
1003 self,
1004 orientation == GTK_ORIENTATION_HORIZONTAL ? "horizontal" : "vertical",
1005 min_value, nat_value,
1006 for_size));
1007
1008 if (minimum != NULL)
1009 *minimum = min_value;
1010
1011 if (natural != NULL)
1012 *natural = nat_value;
1013}
1014
1015static void
1016gtk_constraint_layout_allocate (GtkLayoutManager *manager,
1017 GtkWidget *widget,
1018 int width,
1019 int height,
1020 int baseline)
1021{
1022 GtkConstraintLayout *self = GTK_CONSTRAINT_LAYOUT (ptr: manager);
1023 GtkConstraintRef *stay_w, *stay_h, *stay_t, *stay_l;
1024 GtkConstraintSolver *solver;
1025 GtkConstraintVariable *layout_top, *layout_height;
1026 GtkConstraintVariable *layout_left, *layout_width;
1027 GtkWidget *child;
1028
1029 solver = gtk_constraint_layout_get_solver (self);
1030 if (solver == NULL)
1031 return;
1032
1033 /* We add required stay constraints to ensure that the layout remains
1034 * within the bounds of the allocation
1035 */
1036 layout_top = get_layout_attribute (self, widget, attr: GTK_CONSTRAINT_ATTRIBUTE_TOP);
1037 layout_left = get_layout_attribute (self, widget, attr: GTK_CONSTRAINT_ATTRIBUTE_LEFT);
1038 layout_width = get_layout_attribute (self, widget, attr: GTK_CONSTRAINT_ATTRIBUTE_WIDTH);
1039 layout_height = get_layout_attribute (self, widget, attr: GTK_CONSTRAINT_ATTRIBUTE_HEIGHT);
1040
1041 gtk_constraint_variable_set_value (variable: layout_top, value: 0.0);
1042 stay_t = gtk_constraint_solver_add_stay_variable (solver,
1043 variable: layout_top,
1044 strength: GTK_CONSTRAINT_STRENGTH_REQUIRED);
1045 gtk_constraint_variable_set_value (variable: layout_left, value: 0.0);
1046 stay_l = gtk_constraint_solver_add_stay_variable (solver,
1047 variable: layout_left,
1048 strength: GTK_CONSTRAINT_STRENGTH_REQUIRED);
1049 gtk_constraint_variable_set_value (variable: layout_width, value: width);
1050 stay_w = gtk_constraint_solver_add_stay_variable (solver,
1051 variable: layout_width,
1052 strength: GTK_CONSTRAINT_STRENGTH_REQUIRED);
1053 gtk_constraint_variable_set_value (variable: layout_height, value: height);
1054 stay_h = gtk_constraint_solver_add_stay_variable (solver,
1055 variable: layout_height,
1056 strength: GTK_CONSTRAINT_STRENGTH_REQUIRED);
1057 GTK_NOTE (LAYOUT,
1058 g_print ("Layout [%p]: { .x: %g, .y: %g, .w: %g, .h: %g }\n",
1059 self,
1060 gtk_constraint_variable_get_value (layout_left),
1061 gtk_constraint_variable_get_value (layout_top),
1062 gtk_constraint_variable_get_value (layout_width),
1063 gtk_constraint_variable_get_value (layout_height)));
1064
1065 for (child = _gtk_widget_get_first_child (widget);
1066 child != NULL;
1067 child = _gtk_widget_get_next_sibling (widget: child))
1068 {
1069 GtkConstraintVariable *var_top, *var_left, *var_width, *var_height;
1070 GtkConstraintVariable *var_baseline;
1071 GtkAllocation child_alloc;
1072 int child_baseline = -1;
1073
1074 if (!gtk_widget_should_layout (widget: child))
1075 continue;
1076
1077 /* Retrieve all the values associated with the child */
1078 var_top = get_child_attribute (layout: self, widget: child, attr: GTK_CONSTRAINT_ATTRIBUTE_TOP);
1079 var_left = get_child_attribute (layout: self, widget: child, attr: GTK_CONSTRAINT_ATTRIBUTE_LEFT);
1080 var_width = get_child_attribute (layout: self, widget: child, attr: GTK_CONSTRAINT_ATTRIBUTE_WIDTH);
1081 var_height = get_child_attribute (layout: self, widget: child, attr: GTK_CONSTRAINT_ATTRIBUTE_HEIGHT);
1082 var_baseline = get_child_attribute (layout: self, widget: child, attr: GTK_CONSTRAINT_ATTRIBUTE_BASELINE);
1083
1084 GTK_NOTE (LAYOUT,
1085 g_print ("Allocating child '%s'[%p] with { .x: %g, .y: %g, .w: %g, .h: %g, .b: %g }\n",
1086 gtk_widget_get_name (child), child,
1087 gtk_constraint_variable_get_value (var_left),
1088 gtk_constraint_variable_get_value (var_top),
1089 gtk_constraint_variable_get_value (var_width),
1090 gtk_constraint_variable_get_value (var_height),
1091 gtk_constraint_variable_get_value (var_baseline)));
1092
1093 child_alloc.x = floor (x: gtk_constraint_variable_get_value (variable: var_left));
1094 child_alloc.y = floor (x: gtk_constraint_variable_get_value (variable: var_top));
1095 child_alloc.width = ceil (x: gtk_constraint_variable_get_value (variable: var_width));
1096 child_alloc.height = ceil (x: gtk_constraint_variable_get_value (variable: var_height));
1097
1098 if (gtk_constraint_variable_get_value (variable: var_baseline) > 0)
1099 child_baseline = floor (x: gtk_constraint_variable_get_value (variable: var_baseline));
1100
1101 gtk_widget_size_allocate (GTK_WIDGET (child),
1102 allocation: &child_alloc,
1103 baseline: child_baseline);
1104 }
1105
1106#ifdef G_ENABLE_DEBUG
1107 if (GTK_DEBUG_CHECK (LAYOUT))
1108 {
1109 GHashTableIter iter;
1110 gpointer key;
1111 g_hash_table_iter_init (iter: &iter, hash_table: self->guides);
1112 while (g_hash_table_iter_next (iter: &iter, key: &key, NULL))
1113 {
1114 GtkConstraintGuide *guide = key;
1115 GtkConstraintVariable *var_top, *var_left, *var_width, *var_height;
1116 var_top = gtk_constraint_guide_get_attribute (guide, attr: GTK_CONSTRAINT_ATTRIBUTE_TOP);
1117 var_left = gtk_constraint_guide_get_attribute (guide, attr: GTK_CONSTRAINT_ATTRIBUTE_LEFT);
1118 var_width = gtk_constraint_guide_get_attribute (guide, attr: GTK_CONSTRAINT_ATTRIBUTE_WIDTH);
1119 var_height = gtk_constraint_guide_get_attribute (guide, attr: GTK_CONSTRAINT_ATTRIBUTE_HEIGHT);
1120 g_print (format: "Allocating guide '%s'[%p] with { .x: %g .y: %g .w: %g .h: %g }\n",
1121 gtk_constraint_guide_get_name (guide), guide,
1122 gtk_constraint_variable_get_value (variable: var_left),
1123 gtk_constraint_variable_get_value (variable: var_top),
1124 gtk_constraint_variable_get_value (variable: var_width),
1125 gtk_constraint_variable_get_value (variable: var_height));
1126 }
1127 }
1128#endif
1129
1130 /* The allocation stay constraints are not needed any more */
1131 gtk_constraint_solver_remove_constraint (solver, reference: stay_w);
1132 gtk_constraint_solver_remove_constraint (solver, reference: stay_h);
1133 gtk_constraint_solver_remove_constraint (solver, reference: stay_t);
1134 gtk_constraint_solver_remove_constraint (solver, reference: stay_l);
1135}
1136
1137static void
1138gtk_constraint_layout_root (GtkLayoutManager *manager)
1139{
1140 GtkConstraintLayout *self = GTK_CONSTRAINT_LAYOUT (ptr: manager);
1141 GHashTableIter iter;
1142 GtkWidget *widget;
1143 GtkRoot *root;
1144 gpointer key;
1145
1146 widget = gtk_layout_manager_get_widget (manager);
1147 root = gtk_widget_get_root (widget);
1148
1149 self->solver = gtk_root_get_constraint_solver (self: root);
1150
1151 /* Now that we have a solver, attach all constraints we have */
1152 g_hash_table_iter_init (iter: &iter, hash_table: self->constraints);
1153 while (g_hash_table_iter_next (iter: &iter, key: &key, NULL))
1154 {
1155 GtkConstraint *constraint = key;
1156 layout_add_constraint (self, constraint);
1157 }
1158
1159 g_hash_table_iter_init (iter: &iter, hash_table: self->guides);
1160 while (g_hash_table_iter_next (iter: &iter, key: &key, NULL))
1161 {
1162 GtkConstraintGuide *guide = key;
1163 gtk_constraint_guide_update (guide);
1164 }
1165}
1166
1167static void
1168gtk_constraint_layout_unroot (GtkLayoutManager *manager)
1169{
1170 GtkConstraintLayout *self = GTK_CONSTRAINT_LAYOUT (ptr: manager);
1171 GHashTableIter iter;
1172 gpointer key;
1173
1174 /* Detach all constraints we're holding, as we're removing the layout
1175 * from the global solver, and they should not contribute to the other
1176 * layouts
1177 */
1178 g_hash_table_iter_init (iter: &iter, hash_table: self->constraints);
1179 while (g_hash_table_iter_next (iter: &iter, key: &key, NULL))
1180 {
1181 GtkConstraint *constraint = key;
1182 gtk_constraint_detach (constraint);
1183 }
1184
1185 g_hash_table_iter_init (iter: &iter, hash_table: self->guides);
1186 while (g_hash_table_iter_next (iter: &iter, key: &key, NULL))
1187 {
1188 GtkConstraintGuide *guide = key;
1189 gtk_constraint_guide_detach (guide);
1190 }
1191
1192 self->solver = NULL;
1193}
1194
1195static void
1196gtk_constraint_layout_class_init (GtkConstraintLayoutClass *klass)
1197{
1198 GtkLayoutManagerClass *manager_class = GTK_LAYOUT_MANAGER_CLASS (ptr: klass);
1199 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
1200
1201 gobject_class->finalize = gtk_constraint_layout_finalize;
1202
1203 manager_class->layout_child_type = GTK_TYPE_CONSTRAINT_LAYOUT_CHILD;
1204 manager_class->measure = gtk_constraint_layout_measure;
1205 manager_class->allocate = gtk_constraint_layout_allocate;
1206 manager_class->root = gtk_constraint_layout_root;
1207 manager_class->unroot = gtk_constraint_layout_unroot;
1208}
1209
1210static void
1211gtk_constraint_layout_init (GtkConstraintLayout *self)
1212{
1213 /* The bound variables in the solver */
1214 self->bound_attributes =
1215 g_hash_table_new_full (hash_func: g_str_hash, key_equal_func: g_str_equal,
1216 NULL,
1217 value_destroy_func: (GDestroyNotify) gtk_constraint_variable_unref);
1218
1219 /* The GtkConstraint instances we own */
1220 self->constraints =
1221 g_hash_table_new_full (NULL, NULL,
1222 key_destroy_func: (GDestroyNotify) g_object_unref,
1223 NULL);
1224
1225 self->guides =
1226 g_hash_table_new_full (NULL, NULL,
1227 key_destroy_func: (GDestroyNotify) g_object_unref,
1228 NULL);
1229}
1230
1231typedef struct {
1232 GtkConstraintLayout *layout;
1233 GtkBuilder *builder;
1234 GList *constraints;
1235 GList *guides;
1236} ConstraintsParserData;
1237
1238typedef struct {
1239 char *source_name;
1240 char *source_attr;
1241 char *target_name;
1242 char *target_attr;
1243 char *relation;
1244 char *strength;
1245 double constant;
1246 double multiplier;
1247} ConstraintData;
1248
1249typedef struct {
1250 char *name;
1251 char *strength;
1252 struct {
1253 int min, nat, max;
1254 } sizes[2];
1255} GuideData;
1256
1257static void
1258constraint_data_free (gpointer _data)
1259{
1260 ConstraintData *data = _data;
1261
1262 g_free (mem: data->source_name);
1263 g_free (mem: data->source_attr);
1264 g_free (mem: data->target_name);
1265 g_free (mem: data->target_attr);
1266 g_free (mem: data->relation);
1267 g_free (mem: data->strength);
1268
1269 g_free (mem: data);
1270}
1271
1272static void
1273guide_data_free (gpointer _data)
1274{
1275 GuideData *data = _data;
1276
1277 g_free (mem: data->name);
1278 g_free (mem: data->strength);
1279
1280 g_free (mem: data);
1281}
1282
1283static void
1284parse_double (const char *string,
1285 double *value_p,
1286 double default_value)
1287{
1288 double value;
1289 char *endptr;
1290 int saved_errno;
1291
1292 if (string == NULL || string[0] == '\0')
1293 {
1294 *value_p = default_value;
1295 return;
1296 }
1297
1298 saved_errno = errno;
1299 errno = 0;
1300 value = g_ascii_strtod (nptr: string, endptr: &endptr);
1301 if (errno == 0 && endptr != string)
1302 *value_p = value;
1303 else
1304 *value_p = default_value;
1305
1306 errno = saved_errno;
1307}
1308
1309static void
1310parse_int (const char *string,
1311 int *value_p,
1312 int default_value)
1313{
1314 gint64 value;
1315 char *endptr;
1316 int saved_errno;
1317
1318 if (string == NULL || string[0] == '\0')
1319 {
1320 *value_p = default_value;
1321 return;
1322 }
1323
1324 saved_errno = errno;
1325 errno = 0;
1326 value = g_ascii_strtoll (nptr: string, endptr: &endptr, base: 10);
1327 if (errno == 0 && endptr != string)
1328 *value_p = (int) value;
1329 else
1330 *value_p = default_value;
1331
1332 errno = saved_errno;
1333}
1334
1335static GtkConstraint *
1336constraint_data_to_constraint (const ConstraintData *data,
1337 GtkBuilder *builder,
1338 GHashTable *guides,
1339 GError **error)
1340{
1341 gpointer source, target;
1342 int source_attr, target_attr;
1343 int relation, strength;
1344 gboolean res;
1345
1346 if (g_strcmp0 (str1: data->source_name, str2: "super") == 0)
1347 source = NULL;
1348 else if (data->source_name == NULL)
1349 {
1350 if (data->source_attr != NULL)
1351 {
1352 g_set_error (err: error, GTK_BUILDER_ERROR,
1353 code: GTK_BUILDER_ERROR_INVALID_VALUE,
1354 format: "Constraints without 'source' must also not "
1355 "have a 'source-attribute' attribute");
1356 return NULL;
1357 }
1358
1359 source = NULL;
1360 }
1361 else
1362 {
1363 if (g_hash_table_contains (hash_table: guides, key: data->source_name))
1364 source = g_hash_table_lookup (hash_table: guides, key: data->source_name);
1365 else
1366 source = gtk_builder_get_object (builder, name: data->source_name);
1367
1368 if (source == NULL)
1369 {
1370 g_set_error (err: error, GTK_BUILDER_ERROR,
1371 code: GTK_BUILDER_ERROR_INVALID_VALUE,
1372 format: "Unable to find source '%s' for constraint",
1373 data->source_name);
1374 return NULL;
1375 }
1376 }
1377
1378 if (g_strcmp0 (str1: data->target_name, str2: "super") == 0)
1379 target = NULL;
1380 else
1381 {
1382 if (g_hash_table_contains (hash_table: guides, key: data->target_name))
1383 target = g_hash_table_lookup (hash_table: guides, key: data->target_name);
1384 else
1385 target = gtk_builder_get_object (builder, name: data->target_name);
1386
1387 if (target == NULL)
1388 {
1389 g_set_error (err: error, GTK_BUILDER_ERROR,
1390 code: GTK_BUILDER_ERROR_INVALID_VALUE,
1391 format: "Unable to find target '%s' for constraint",
1392 data->target_name);
1393 return NULL;
1394 }
1395 }
1396
1397 if (data->source_attr != NULL)
1398 {
1399 res = _gtk_builder_enum_from_string (type: GTK_TYPE_CONSTRAINT_ATTRIBUTE,
1400 string: data->source_attr,
1401 enum_value: &source_attr,
1402 error);
1403 if (!res)
1404 return NULL;
1405 }
1406 else
1407 source_attr = GTK_CONSTRAINT_ATTRIBUTE_NONE;
1408
1409 res = _gtk_builder_enum_from_string (type: GTK_TYPE_CONSTRAINT_ATTRIBUTE,
1410 string: data->target_attr,
1411 enum_value: &target_attr,
1412 error);
1413 if (!res)
1414 return NULL;
1415
1416 if (data->relation != NULL)
1417 {
1418 res = _gtk_builder_enum_from_string (type: GTK_TYPE_CONSTRAINT_RELATION,
1419 string: data->relation,
1420 enum_value: &relation,
1421 error);
1422 if (!res)
1423 return NULL;
1424 }
1425 else
1426 relation = GTK_CONSTRAINT_RELATION_EQ;
1427
1428 if (data->strength != NULL)
1429 {
1430 res = _gtk_builder_enum_from_string (type: GTK_TYPE_CONSTRAINT_STRENGTH,
1431 string: data->strength,
1432 enum_value: &strength,
1433 error);
1434 if (!res)
1435 return NULL;
1436 }
1437 else
1438 strength = GTK_CONSTRAINT_STRENGTH_REQUIRED;
1439
1440 if (source == NULL && source_attr == GTK_CONSTRAINT_ATTRIBUTE_NONE)
1441 return gtk_constraint_new_constant (target, target_attribute: target_attr,
1442 relation,
1443 constant: data->constant,
1444 strength);
1445 else
1446 return gtk_constraint_new (target, target_attribute: target_attr,
1447 relation,
1448 source, source_attribute: source_attr,
1449 multiplier: data->multiplier,
1450 constant: data->constant,
1451 strength);
1452}
1453
1454static GtkConstraintGuide *
1455guide_data_to_guide (const GuideData *data,
1456 GtkBuilder *builder,
1457 GError **error)
1458{
1459 int strength;
1460 gboolean res;
1461
1462 if (data->strength != NULL)
1463 {
1464 res = _gtk_builder_enum_from_string (type: GTK_TYPE_CONSTRAINT_STRENGTH,
1465 string: data->strength,
1466 enum_value: &strength,
1467 error);
1468 if (!res)
1469 return NULL;
1470 }
1471 else
1472 strength = GTK_CONSTRAINT_STRENGTH_MEDIUM;
1473
1474 return g_object_new (GTK_TYPE_CONSTRAINT_GUIDE,
1475 first_property_name: "min-width", data->sizes[GTK_ORIENTATION_HORIZONTAL].min,
1476 "nat-width", data->sizes[GTK_ORIENTATION_HORIZONTAL].nat,
1477 "max-width", data->sizes[GTK_ORIENTATION_HORIZONTAL].max,
1478 "min-height", data->sizes[GTK_ORIENTATION_VERTICAL].min,
1479 "nat-height", data->sizes[GTK_ORIENTATION_VERTICAL].nat,
1480 "max-height", data->sizes[GTK_ORIENTATION_VERTICAL].max,
1481 "strength", strength,
1482 "name", data->name,
1483 NULL);
1484}
1485
1486static void
1487constraints_start_element (GtkBuildableParseContext *context,
1488 const char *element_name,
1489 const char **attr_names,
1490 const char **attr_values,
1491 gpointer user_data,
1492 GError **error)
1493{
1494 ConstraintsParserData *data = user_data;
1495
1496 if (strcmp (s1: element_name, s2: "constraints") == 0)
1497 {
1498 if (!_gtk_builder_check_parent (builder: data->builder, context, parent_name: "object", error))
1499 return;
1500
1501 if (!g_markup_collect_attributes (element_name, attribute_names: attr_names, attribute_values: attr_values, error,
1502 first_type: G_MARKUP_COLLECT_INVALID, NULL, NULL,
1503 G_MARKUP_COLLECT_INVALID))
1504 _gtk_builder_prefix_error (builder: data->builder, context, error);
1505 }
1506 else if (strcmp (s1: element_name, s2: "constraint") == 0)
1507 {
1508 const char *target_name, *target_attribute;
1509 const char *relation_str = NULL;
1510 const char *source_name = NULL, *source_attribute = NULL;
1511 const char *multiplier_str = NULL, *constant_str = NULL;
1512 const char *strength_str = NULL;
1513 ConstraintData *cdata;
1514
1515 if (!_gtk_builder_check_parent (builder: data->builder, context, parent_name: "constraints", error))
1516 return;
1517
1518 if (!g_markup_collect_attributes (element_name, attribute_names: attr_names, attribute_values: attr_values, error,
1519 first_type: G_MARKUP_COLLECT_STRING, first_attr: "target", &target_name,
1520 G_MARKUP_COLLECT_STRING, "target-attribute", &target_attribute,
1521 G_MARKUP_COLLECT_STRING, "relation", &relation_str,
1522 G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "source", &source_name,
1523 G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "source-attribute", &source_attribute,
1524 G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "multiplier", &multiplier_str,
1525 G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "constant", &constant_str,
1526 G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "strength", &strength_str,
1527 G_MARKUP_COLLECT_INVALID))
1528 {
1529 _gtk_builder_prefix_error (builder: data->builder, context, error);
1530 return;
1531 }
1532
1533 cdata = g_new0 (ConstraintData, 1);
1534 cdata->target_name = g_strdup (str: target_name);
1535 cdata->target_attr = g_strdup (str: target_attribute);
1536 cdata->relation = g_strdup (str: relation_str);
1537 cdata->source_name = g_strdup (str: source_name);
1538 cdata->source_attr = g_strdup (str: source_attribute);
1539 parse_double (string: multiplier_str, value_p: &cdata->multiplier, default_value: 1.0);
1540 parse_double (string: constant_str, value_p: &cdata->constant, default_value: 0.0);
1541 cdata->strength = g_strdup (str: strength_str);
1542
1543 data->constraints = g_list_prepend (list: data->constraints, data: cdata);
1544 }
1545 else if (strcmp (s1: element_name, s2: "guide") == 0)
1546 {
1547 const char *min_width, *nat_width, *max_width;
1548 const char *min_height, *nat_height, *max_height;
1549 const char *strength_str;
1550 const char *name;
1551 GuideData *gdata;
1552
1553 if (!_gtk_builder_check_parent (builder: data->builder, context, parent_name: "constraints", error))
1554 return;
1555
1556 if (!g_markup_collect_attributes (element_name, attribute_names: attr_names, attribute_values: attr_values, error,
1557 first_type: G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, first_attr: "min-width", &min_width,
1558 G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "nat-width", &nat_width,
1559 G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "max-width", &max_width,
1560 G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "min-height", &min_height,
1561 G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "nat-height", &nat_height,
1562 G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "max-height", &max_height,
1563 G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "strength", &strength_str,
1564 G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "name", &name,
1565 G_MARKUP_COLLECT_INVALID))
1566 {
1567 _gtk_builder_prefix_error (builder: data->builder, context, error);
1568 return;
1569 }
1570
1571 gdata = g_new0 (GuideData, 1);
1572 parse_int (string: min_width, value_p: &(gdata->sizes[GTK_ORIENTATION_HORIZONTAL].min), default_value: 0);
1573 parse_int (string: nat_width, value_p: &(gdata->sizes[GTK_ORIENTATION_HORIZONTAL].nat), default_value: 0);
1574 parse_int (string: max_width, value_p: &(gdata->sizes[GTK_ORIENTATION_HORIZONTAL].max), G_MAXINT);
1575 parse_int (string: min_height, value_p: &(gdata->sizes[GTK_ORIENTATION_VERTICAL].min), default_value: 0);
1576 parse_int (string: nat_height, value_p: &(gdata->sizes[GTK_ORIENTATION_VERTICAL].nat), default_value: 0);
1577 parse_int (string: max_height, value_p: &(gdata->sizes[GTK_ORIENTATION_VERTICAL].max), G_MAXINT);
1578 gdata->name = g_strdup (str: name);
1579 gdata->strength = g_strdup (str: strength_str);
1580
1581 data->guides = g_list_prepend (list: data->guides, data: gdata);
1582 }
1583 else
1584 {
1585 _gtk_builder_error_unhandled_tag (builder: data->builder, context,
1586 object: "GtkConstraintLayout", element_name,
1587 error);
1588 }
1589}
1590
1591static void
1592constraints_end_element (GtkBuildableParseContext *context,
1593 const char *element_name,
1594 gpointer user_data,
1595 GError **error)
1596{
1597}
1598
1599static const GtkBuildableParser constraints_parser = {
1600 constraints_start_element,
1601 constraints_end_element,
1602 NULL,
1603};
1604
1605static gboolean
1606gtk_constraint_layout_custom_tag_start (GtkBuildable *buildable,
1607 GtkBuilder *builder,
1608 GObject *child,
1609 const char *element_name,
1610 GtkBuildableParser *parser,
1611 gpointer *parser_data)
1612{
1613 if (strcmp (s1: element_name, s2: "constraints") == 0)
1614 {
1615 ConstraintsParserData *data = g_new (ConstraintsParserData, 1);
1616
1617 data->layout = g_object_ref (GTK_CONSTRAINT_LAYOUT (buildable));
1618 data->builder = builder;
1619 data->constraints = NULL;
1620 data->guides = NULL;
1621
1622 *parser = constraints_parser;
1623 *parser_data = data;
1624
1625 return TRUE;
1626 }
1627
1628 return FALSE;
1629}
1630
1631static void
1632gtk_constraint_layout_custom_tag_end (GtkBuildable *buildable,
1633 GtkBuilder *builder,
1634 GObject *child,
1635 const char *element_name,
1636 gpointer data)
1637{
1638}
1639
1640static void
1641gtk_constraint_layout_custom_finished (GtkBuildable *buildable,
1642 GtkBuilder *builder,
1643 GObject *child,
1644 const char *element_name,
1645 gpointer user_data)
1646{
1647 ConstraintsParserData *data = user_data;
1648
1649 if (strcmp (s1: element_name, s2: "constraints") == 0)
1650 {
1651 GList *l;
1652 GHashTable *guides;
1653
1654 guides = g_hash_table_new (hash_func: g_str_hash, key_equal_func: g_str_equal);
1655
1656 data->guides = g_list_reverse (list: data->guides);
1657 for (l = data->guides; l != NULL; l = l->next)
1658 {
1659 const GuideData *gdata = l->data;
1660 GtkConstraintGuide *g;
1661 GError *error = NULL;
1662 const char *name;
1663
1664 g = guide_data_to_guide (data: gdata, builder, error: &error);
1665 if (error != NULL)
1666 {
1667 g_critical ("Unable to parse guide definition: %s", error->message);
1668 g_error_free (error);
1669 continue;
1670 }
1671
1672 name = gtk_constraint_guide_get_name (guide: g);
1673 if (g_hash_table_lookup (hash_table: guides, key: name))
1674 {
1675 g_critical ("Duplicate guide: %s", name);
1676 g_object_unref (object: g);
1677 continue;
1678 }
1679
1680 g_hash_table_insert (hash_table: guides, key: (gpointer)name, value: g);
1681 gtk_constraint_layout_add_guide (layout: data->layout, guide: g);
1682 }
1683
1684 data->constraints = g_list_reverse (list: data->constraints);
1685 for (l = data->constraints; l != NULL; l = l->next)
1686 {
1687 const ConstraintData *cdata = l->data;
1688 GtkConstraint *c;
1689 GError *error = NULL;
1690
1691 c = constraint_data_to_constraint (data: cdata, builder, guides, error: &error);
1692 if (error != NULL)
1693 {
1694 g_critical ("Unable to parse constraint definition '%s.%s [%s] %s.%s * %g + %g': %s",
1695 cdata->target_name, cdata->target_attr,
1696 cdata->relation,
1697 cdata->source_name, cdata->source_attr,
1698 cdata->multiplier,
1699 cdata->constant,
1700 error->message);
1701 g_error_free (error);
1702 continue;
1703 }
1704
1705 layout_add_constraint (self: data->layout, constraint: c);
1706 g_hash_table_add (hash_table: data->layout->constraints, key: c);
1707 if (data->layout->constraints_observer)
1708 g_list_store_append (store: data->layout->constraints_observer, item: c);
1709 }
1710
1711 gtk_layout_manager_layout_changed (manager: GTK_LAYOUT_MANAGER (ptr: data->layout));
1712
1713 g_list_free_full (list: data->constraints, free_func: constraint_data_free);
1714 g_list_free_full (list: data->guides, free_func: guide_data_free);
1715 g_object_unref (object: data->layout);
1716 g_free (mem: data);
1717
1718 g_hash_table_unref (hash_table: guides);
1719 }
1720}
1721
1722static void
1723gtk_buildable_interface_init (GtkBuildableIface *iface)
1724{
1725 iface->custom_tag_start = gtk_constraint_layout_custom_tag_start;
1726 iface->custom_tag_end = gtk_constraint_layout_custom_tag_end;
1727 iface->custom_finished = gtk_constraint_layout_custom_finished;
1728}
1729
1730/**
1731 * gtk_constraint_layout_new:
1732 *
1733 * Creates a new `GtkConstraintLayout` layout manager.
1734 *
1735 * Returns: the newly created `GtkConstraintLayout`
1736 */
1737GtkLayoutManager *
1738gtk_constraint_layout_new (void)
1739{
1740 return g_object_new (GTK_TYPE_CONSTRAINT_LAYOUT, NULL);
1741}
1742
1743/**
1744 * gtk_constraint_layout_add_constraint:
1745 * @layout: a `GtkConstraintLayout`
1746 * @constraint: (transfer full): a [class@Gtk.Constraint]
1747 *
1748 * Adds a constraint to the layout manager.
1749 *
1750 * The [property@Gtk.Constraint:source] and [property@Gtk.Constraint:target]
1751 * properties of `constraint` can be:
1752 *
1753 * - set to `NULL` to indicate that the constraint refers to the
1754 * widget using `layout`
1755 * - set to the [class@Gtk.Widget] using `layout`
1756 * - set to a child of the [class@Gtk.Widget] using `layout`
1757 * - set to a [class@Gtk.ConstraintGuide] that is part of `layout`
1758 *
1759 * The @layout acquires the ownership of @constraint after calling
1760 * this function.
1761 */
1762void
1763gtk_constraint_layout_add_constraint (GtkConstraintLayout *layout,
1764 GtkConstraint *constraint)
1765{
1766 g_return_if_fail (GTK_IS_CONSTRAINT_LAYOUT (layout));
1767 g_return_if_fail (GTK_IS_CONSTRAINT (constraint));
1768 g_return_if_fail (!gtk_constraint_is_attached (constraint));
1769
1770 layout_add_constraint (self: layout, constraint);
1771
1772 g_hash_table_add (hash_table: layout->constraints, key: constraint);
1773 if (layout->constraints_observer)
1774 g_list_store_append (store: layout->constraints_observer, item: constraint);
1775
1776 gtk_layout_manager_layout_changed (manager: GTK_LAYOUT_MANAGER (ptr: layout));
1777}
1778
1779static void
1780list_store_remove_item (GListStore *store,
1781 gpointer item)
1782{
1783 int n_items;
1784 int i;
1785
1786 n_items = g_list_model_get_n_items (list: G_LIST_MODEL (ptr: store));
1787 for (i = 0; i < n_items; i++)
1788 {
1789 gpointer *model_item = g_list_model_get_item (list: G_LIST_MODEL (ptr: store), position: i);
1790 g_object_unref (object: model_item);
1791 if (item == model_item)
1792 {
1793 g_list_store_remove (store, position: i);
1794 break;
1795 }
1796 }
1797}
1798
1799/**
1800 * gtk_constraint_layout_remove_constraint:
1801 * @layout: a `GtkConstraintLayout`
1802 * @constraint: a [class@Gtk.Constraint]
1803 *
1804 * Removes `constraint` from the layout manager,
1805 * so that it no longer influences the layout.
1806 */
1807void
1808gtk_constraint_layout_remove_constraint (GtkConstraintLayout *layout,
1809 GtkConstraint *constraint)
1810{
1811 g_return_if_fail (GTK_IS_CONSTRAINT_LAYOUT (layout));
1812 g_return_if_fail (GTK_IS_CONSTRAINT (constraint));
1813 g_return_if_fail (gtk_constraint_is_attached (constraint));
1814
1815 gtk_constraint_detach (constraint);
1816 g_hash_table_remove (hash_table: layout->constraints, key: constraint);
1817 if (layout->constraints_observer)
1818 list_store_remove_item (store: layout->constraints_observer, item: constraint);
1819
1820 gtk_layout_manager_layout_changed (manager: GTK_LAYOUT_MANAGER (ptr: layout));
1821}
1822
1823/**
1824 * gtk_constraint_layout_remove_all_constraints:
1825 * @layout: a `GtkConstraintLayout`
1826 *
1827 * Removes all constraints from the layout manager.
1828 */
1829void
1830gtk_constraint_layout_remove_all_constraints (GtkConstraintLayout *layout)
1831{
1832 GHashTableIter iter;
1833 gpointer key;
1834
1835 g_return_if_fail (GTK_IS_CONSTRAINT_LAYOUT (layout));
1836
1837 g_hash_table_iter_init (iter: &iter, hash_table: layout->constraints);
1838 while (g_hash_table_iter_next (iter: &iter, key: &key, NULL))
1839 {
1840 GtkConstraint *constraint = key;
1841
1842 gtk_constraint_detach (constraint);
1843 g_hash_table_iter_remove (iter: &iter);
1844 }
1845 if (layout->constraints_observer)
1846 g_list_store_remove_all (store: layout->constraints_observer);
1847
1848 gtk_layout_manager_layout_changed (manager: GTK_LAYOUT_MANAGER (ptr: layout));
1849}
1850
1851/**
1852 * gtk_constraint_layout_add_guide:
1853 * @layout: a `GtkConstraintLayout`
1854 * @guide: (transfer full): a [class@Gtk.ConstraintGuide] object
1855 *
1856 * Adds a guide to `layout`.
1857 *
1858 * A guide can be used as the source or target of constraints,
1859 * like a widget, but it is not visible.
1860 *
1861 * The `layout` acquires the ownership of `guide` after calling
1862 * this function.
1863 */
1864void
1865gtk_constraint_layout_add_guide (GtkConstraintLayout *layout,
1866 GtkConstraintGuide *guide)
1867{
1868 g_return_if_fail (GTK_IS_CONSTRAINT_LAYOUT (layout));
1869 g_return_if_fail (GTK_IS_CONSTRAINT_GUIDE (guide));
1870 g_return_if_fail (gtk_constraint_guide_get_layout (guide) == NULL);
1871
1872 gtk_constraint_guide_set_layout (guide, layout);
1873 g_hash_table_add (hash_table: layout->guides, key: guide);
1874 if (layout->guides_observer)
1875 g_list_store_append (store: layout->guides_observer, item: guide);
1876
1877 gtk_constraint_guide_update (guide);
1878
1879 gtk_layout_manager_layout_changed (manager: GTK_LAYOUT_MANAGER (ptr: layout));
1880
1881}
1882
1883/**
1884 * gtk_constraint_layout_remove_guide:
1885 * @layout: a `GtkConstraintLayout`
1886 * @guide: a [class@Gtk.ConstraintGuide] object
1887 *
1888 * Removes `guide` from the layout manager,
1889 * so that it no longer influences the layout.
1890 */
1891void
1892gtk_constraint_layout_remove_guide (GtkConstraintLayout *layout,
1893 GtkConstraintGuide *guide)
1894{
1895 g_return_if_fail (GTK_IS_CONSTRAINT_LAYOUT (layout));
1896 g_return_if_fail (GTK_IS_CONSTRAINT_GUIDE (guide));
1897 g_return_if_fail (gtk_constraint_guide_get_layout (guide) == layout);
1898
1899 gtk_constraint_guide_detach (guide);
1900
1901 gtk_constraint_guide_set_layout (guide, NULL);
1902 g_hash_table_remove (hash_table: layout->guides, key: guide);
1903 if (layout->guides_observer)
1904 list_store_remove_item (store: layout->guides_observer, item: guide);
1905
1906 gtk_layout_manager_layout_changed (manager: GTK_LAYOUT_MANAGER (ptr: layout));
1907}
1908
1909static GtkConstraintAttribute
1910attribute_from_name (const char *name)
1911{
1912 if (name == NULL || *name == '\0')
1913 return GTK_CONSTRAINT_ATTRIBUTE_NONE;
1914
1915 /* We sadly need to special case these two because the name does
1916 * not match the VFL grammar rules
1917 */
1918 if (strcmp (s1: name, s2: "centerX") == 0)
1919 return GTK_CONSTRAINT_ATTRIBUTE_CENTER_X;
1920
1921 if (strcmp (s1: name, s2: "centerY") == 0)
1922 return GTK_CONSTRAINT_ATTRIBUTE_CENTER_Y;
1923
1924 for (int i = 0; i < G_N_ELEMENTS (attribute_names); i++)
1925 {
1926 if (strcmp (s1: attribute_names[i], s2: name) == 0)
1927 return i;
1928 }
1929
1930 return GTK_CONSTRAINT_ATTRIBUTE_NONE;
1931}
1932
1933GQuark
1934gtk_constraint_vfl_parser_error_quark (void)
1935{
1936 return g_quark_from_static_string (string: "gtk-constraint-vfl-parser-error-quark");
1937}
1938
1939/**
1940 * gtk_constraint_layout_add_constraints_from_descriptionv: (rename-to gtk_constraint_layout_add_constraints_from_description)
1941 * @layout: a `GtkConstraintLayout`
1942 * @lines: (array length=n_lines): an array of Visual Format Language lines
1943 * defining a set of constraints
1944 * @n_lines: the number of lines
1945 * @hspacing: default horizontal spacing value, or -1 for the fallback value
1946 * @vspacing: default vertical spacing value, or -1 for the fallback value
1947 * @views: (element-type utf8 Gtk.ConstraintTarget): a dictionary of `[ name, target ]`
1948 * pairs; the `name` keys map to the view names in the VFL lines, while
1949 * the `target` values map to children of the widget using a `GtkConstraintLayout`,
1950 * or guides
1951 * @error: return location for a `GError`
1952 *
1953 * Creates a list of constraints from a VFL description.
1954 *
1955 * The Visual Format Language, VFL, is based on Apple's AutoLayout [VFL](https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/VisualFormatLanguage.html).
1956 *
1957 * The `views` dictionary is used to match [iface@Gtk.ConstraintTarget]
1958 * instances to the symbolic view name inside the VFL.
1959 *
1960 * The VFL grammar is:
1961 *
1962 * ```
1963 * <visualFormatString> = (<orientation>)?
1964 * (<superview><connection>)?
1965 * <view>(<connection><view>)*
1966 * (<connection><superview>)?
1967 * <orientation> = 'H' | 'V'
1968 * <superview> = '|'
1969 * <connection> = '' | '-' <predicateList> '-' | '-'
1970 * <predicateList> = <simplePredicate> | <predicateListWithParens>
1971 * <simplePredicate> = <metricName> | <positiveNumber>
1972 * <predicateListWithParens> = '(' <predicate> (',' <predicate>)* ')'
1973 * <predicate> = (<relation>)? <objectOfPredicate> (<operatorList>)? ('@' <priority>)?
1974 * <relation> = '==' | '<=' | '>='
1975 * <objectOfPredicate> = <constant> | <viewName> | ('.' <attributeName>)?
1976 * <priority> = <positiveNumber> | 'required' | 'strong' | 'medium' | 'weak'
1977 * <constant> = <number>
1978 * <operatorList> = (<multiplyOperator>)? (<addOperator>)?
1979 * <multiplyOperator> = [ '*' | '/' ] <positiveNumber>
1980 * <addOperator> = [ '+' | '-' ] <positiveNumber>
1981 * <viewName> = [A-Za-z_]([A-Za-z0-9_]*) // A C identifier
1982 * <metricName> = [A-Za-z_]([A-Za-z0-9_]*) // A C identifier
1983 * <attributeName> = 'top' | 'bottom' | 'left' | 'right' | 'width' | 'height' |
1984 * 'start' | 'end' | 'centerX' | 'centerY' | 'baseline'
1985 * <positiveNumber> // A positive real number parseable by g_ascii_strtod()
1986 * <number> // A real number parseable by g_ascii_strtod()
1987 * ```
1988 *
1989 * **Note**: The VFL grammar used by GTK is slightly different than the one
1990 * defined by Apple, as it can use symbolic values for the constraint's
1991 * strength instead of numeric values; additionally, GTK allows adding
1992 * simple arithmetic operations inside predicates.
1993 *
1994 * Examples of VFL descriptions are:
1995 *
1996 * ```
1997 * // Default spacing
1998 * [button]-[textField]
1999 *
2000 * // Width constraint
2001 * [button(>=50)]
2002 *
2003 * // Connection to super view
2004 * |-50-[purpleBox]-50-|
2005 *
2006 * // Vertical layout
2007 * V:[topField]-10-[bottomField]
2008 *
2009 * // Flush views
2010 * [maroonView][blueView]
2011 *
2012 * // Priority
2013 * [button(100@strong)]
2014 *
2015 * // Equal widths
2016 * [button1(==button2)]
2017 *
2018 * // Multiple predicates
2019 * [flexibleButton(>=70,<=100)]
2020 *
2021 * // A complete line of layout
2022 * |-[find]-[findNext]-[findField(>=20)]-|
2023 *
2024 * // Operators
2025 * [button1(button2 / 3 + 50)]
2026 *
2027 * // Named attributes
2028 * [button1(==button2.height)]
2029 * ```
2030 *
2031 * Returns: (transfer container) (element-type GtkConstraint): the list of
2032 * [class@Gtk.Constraint] instances that were added to the layout
2033 */
2034GList *
2035gtk_constraint_layout_add_constraints_from_descriptionv (GtkConstraintLayout *layout,
2036 const char * const lines[],
2037 gsize n_lines,
2038 int hspacing,
2039 int vspacing,
2040 GHashTable *views,
2041 GError **error)
2042{
2043 GtkConstraintVflParser *parser;
2044 GList *res = NULL;
2045
2046 g_return_val_if_fail (GTK_IS_CONSTRAINT_LAYOUT (layout), NULL);
2047 g_return_val_if_fail (lines != NULL, NULL);
2048 g_return_val_if_fail (views != NULL, NULL);
2049 g_return_val_if_fail (error == NULL || *error == NULL, NULL);
2050
2051 parser = gtk_constraint_vfl_parser_new ();
2052 gtk_constraint_vfl_parser_set_default_spacing (parser, hspacing, vspacing);
2053 gtk_constraint_vfl_parser_set_views (parser, views);
2054
2055 for (gsize i = 0; i < n_lines; i++)
2056 {
2057 const char *line = lines[i];
2058 GError *internal_error = NULL;
2059
2060 gtk_constraint_vfl_parser_parse_line (parser, line, len: -1, error: &internal_error);
2061 if (internal_error != NULL)
2062 {
2063 int offset = gtk_constraint_vfl_parser_get_error_offset (parser);
2064 int range = gtk_constraint_vfl_parser_get_error_range (parser);
2065 char *squiggly = NULL;
2066
2067 if (range > 0)
2068 squiggly = g_strnfill (length: range, fill_char: '~');
2069
2070 g_set_error (err: error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
2071 code: internal_error->code,
2072 format: "%" G_GSIZE_FORMAT ":%d: %s\n"
2073 "%s\n"
2074 "%*s^%s",
2075 i, offset + 1,
2076 internal_error->message,
2077 line,
2078 offset, " ", squiggly != NULL ? squiggly : "");
2079
2080 g_free (mem: squiggly);
2081 g_error_free (error: internal_error);
2082 gtk_constraint_vfl_parser_free (parser);
2083 return res;
2084 }
2085
2086 int n_constraints = 0;
2087 GtkConstraintVfl *constraints = gtk_constraint_vfl_parser_get_constraints (parser, n_constraints: &n_constraints);
2088 for (int j = 0; j < n_constraints; j++)
2089 {
2090 const GtkConstraintVfl *c = &constraints[j];
2091 gpointer source, target;
2092 GtkConstraintAttribute source_attr, target_attr;
2093
2094 target = g_hash_table_lookup (hash_table: views, key: c->view1);
2095 target_attr = attribute_from_name (name: c->attr1);
2096
2097 if (c->view2 != NULL)
2098 source = g_hash_table_lookup (hash_table: views, key: c->view2);
2099 else
2100 source = NULL;
2101
2102 if (c->attr2 != NULL)
2103 source_attr = attribute_from_name (name: c->attr2);
2104 else
2105 source_attr = GTK_CONSTRAINT_ATTRIBUTE_NONE;
2106
2107 GtkConstraint *constraint =
2108 gtk_constraint_new (target, target_attribute: target_attr,
2109 relation: c->relation,
2110 source, source_attribute: source_attr,
2111 multiplier: c->multiplier,
2112 constant: c->constant,
2113 strength: c->strength);
2114
2115 layout_add_constraint (self: layout, constraint);
2116 g_hash_table_add (hash_table: layout->constraints, key: constraint);
2117 if (layout->constraints_observer)
2118 g_list_store_append (store: layout->constraints_observer, item: constraint);
2119
2120 res = g_list_prepend (list: res, data: constraint);
2121 }
2122
2123 g_free (mem: constraints);
2124 }
2125
2126 gtk_constraint_vfl_parser_free (parser);
2127
2128 gtk_layout_manager_layout_changed (manager: GTK_LAYOUT_MANAGER (ptr: layout));
2129
2130 return res;
2131}
2132
2133/**
2134 * gtk_constraint_layout_add_constraints_from_description:
2135 * @layout: a `GtkConstraintLayout`
2136 * @lines: (array length=n_lines): an array of Visual Format Language lines
2137 * defining a set of constraints
2138 * @n_lines: the number of lines
2139 * @hspacing: default horizontal spacing value, or -1 for the fallback value
2140 * @vspacing: default vertical spacing value, or -1 for the fallback value
2141 * @error: return location for a `GError`
2142 * @first_view: the name of a view in the VFL description, followed by the
2143 * [iface@Gtk.ConstraintTarget] to which it maps
2144 * @...: a `NULL`-terminated list of view names and [iface@Gtk.ConstraintTarget]s
2145 *
2146 * Creates a list of constraints from a VFL description.
2147 *
2148 * This function is a convenience wrapper around
2149 * [method@Gtk.ConstraintLayout.add_constraints_from_descriptionv], using
2150 * variadic arguments to populate the view/target map.
2151 *
2152 * Returns: (transfer container) (element-type Gtk.Constraint): the list of
2153 * [class@Gtk.Constraint]s that were added to the layout
2154 */
2155GList *
2156gtk_constraint_layout_add_constraints_from_description (GtkConstraintLayout *layout,
2157 const char * const lines[],
2158 gsize n_lines,
2159 int hspacing,
2160 int vspacing,
2161 GError **error,
2162 const char *first_view,
2163 ...)
2164{
2165 GtkConstraintVflParser *parser;
2166 GHashTable *views;
2167 const char *view;
2168 GList *res;
2169 va_list args;
2170
2171 g_return_val_if_fail (GTK_IS_CONSTRAINT_LAYOUT (layout), NULL);
2172 g_return_val_if_fail (lines != NULL, NULL);
2173 g_return_val_if_fail (error == NULL || *error == NULL, NULL);
2174 g_return_val_if_fail (first_view != NULL, NULL);
2175
2176 parser = gtk_constraint_vfl_parser_new ();
2177 gtk_constraint_vfl_parser_set_default_spacing (parser, hspacing, vspacing);
2178
2179 views = g_hash_table_new (hash_func: g_str_hash, key_equal_func: g_str_equal);
2180
2181 va_start (args, first_view);
2182
2183 view = first_view;
2184 while (view != NULL)
2185 {
2186 GtkConstraintTarget *target = va_arg (args, GtkConstraintTarget *);
2187
2188 if (target == NULL)
2189 break;
2190
2191 g_hash_table_insert (hash_table: views, key: (gpointer) view, value: target);
2192
2193 view = va_arg (args, const char *);
2194 }
2195
2196 va_end (args);
2197
2198 res =
2199 gtk_constraint_layout_add_constraints_from_descriptionv (layout, lines, n_lines,
2200 hspacing, vspacing,
2201 views,
2202 error);
2203
2204 g_hash_table_unref (hash_table: views);
2205
2206 return res;
2207}
2208
2209/**
2210 * gtk_constraint_layout_observe_constraints:
2211 * @layout: a `GtkConstraintLayout`
2212 *
2213 * Returns a `GListModel` to track the constraints that are
2214 * part of the layout.
2215 *
2216 * Calling this function will enable extra internal bookkeeping
2217 * to track constraints and emit signals on the returned listmodel.
2218 * It may slow down operations a lot.
2219 *
2220 * Applications should try hard to avoid calling this function
2221 * because of the slowdowns.
2222 *
2223 * Returns: (transfer full) (attributes element-type=GtkConstraint): a
2224 * `GListModel` tracking the layout's constraints
2225 */
2226GListModel *
2227gtk_constraint_layout_observe_constraints (GtkConstraintLayout *layout)
2228{
2229 GHashTableIter iter;
2230 gpointer key;
2231
2232 if (layout->constraints_observer)
2233 return g_object_ref (G_LIST_MODEL (layout->constraints_observer));
2234
2235 layout->constraints_observer = g_list_store_new (GTK_TYPE_CONSTRAINT);
2236 g_object_add_weak_pointer (object: (GObject *)layout->constraints_observer,
2237 weak_pointer_location: (gpointer *)&layout->constraints_observer);
2238
2239 g_hash_table_iter_init (iter: &iter, hash_table: layout->constraints);
2240 while (g_hash_table_iter_next (iter: &iter, key: &key, NULL))
2241 {
2242 GtkConstraint *constraint = key;
2243 g_list_store_append (store: layout->constraints_observer, item: constraint);
2244 }
2245
2246 return G_LIST_MODEL (ptr: layout->constraints_observer);
2247}
2248
2249/**
2250 * gtk_constraint_layout_observe_guides:
2251 * @layout: a `GtkConstraintLayout`
2252 *
2253 * Returns a `GListModel` to track the guides that are
2254 * part of the layout.
2255 *
2256 * Calling this function will enable extra internal bookkeeping
2257 * to track guides and emit signals on the returned listmodel.
2258 * It may slow down operations a lot.
2259 *
2260 * Applications should try hard to avoid calling this function
2261 * because of the slowdowns.
2262 *
2263 * Returns: (transfer full) (attributes element-type=GtkConstraintGuide): a
2264 * `GListModel` tracking the layout's guides
2265 */
2266GListModel *
2267gtk_constraint_layout_observe_guides (GtkConstraintLayout *layout)
2268{
2269 GHashTableIter iter;
2270 gpointer key;
2271
2272 if (layout->guides_observer)
2273 return g_object_ref (G_LIST_MODEL (layout->guides_observer));
2274
2275 layout->guides_observer = g_list_store_new (GTK_TYPE_CONSTRAINT_GUIDE);
2276 g_object_add_weak_pointer (object: (GObject *)layout->guides_observer,
2277 weak_pointer_location: (gpointer *)&layout->guides_observer);
2278
2279 g_hash_table_iter_init (iter: &iter, hash_table: layout->guides);
2280 while (g_hash_table_iter_next (iter: &iter, key: &key, NULL))
2281 {
2282 GtkConstraintGuide *guide = key;
2283 g_list_store_append (store: layout->guides_observer, item: guide);
2284 }
2285
2286 return G_LIST_MODEL (ptr: layout->guides_observer);
2287}
2288

source code of gtk/gtk/gtkconstraintlayout.c