1/* gtkconstraintguide.c: Flexible space for constraints
2 * Copyright 2019 Red Hat, Inc.
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: Matthias Clasen
18 */
19
20/**
21 * GtkConstraintGuide:
22 *
23 * A `GtkConstraintGuide` is an invisible layout element in a
24 * `GtkConstraintLayout`.
25 *
26 * The `GtkConstraintLayout` treats guides like widgets. They
27 * can be used as the source or target of a `GtkConstraint`.
28 *
29 * Guides have a minimum, maximum and natural size. Depending
30 * on the constraints that are applied, they can act like a
31 * guideline that widgets can be aligned to, or like *flexible
32 * space*.
33 *
34 * Unlike a `GtkWidget`, a `GtkConstraintGuide` will not be drawn.
35 */
36
37#include "config.h"
38
39#include "gtkconstraintguide.h"
40
41#include "gtkconstraintguideprivate.h"
42#include "gtkconstraintlayoutprivate.h"
43#include "gtkconstraintexpressionprivate.h"
44#include "gtkconstraintsolverprivate.h"
45
46#include "gtkdebug.h"
47#include "gtkintl.h"
48#include "gtkprivate.h"
49
50
51typedef enum {
52 MIN_WIDTH,
53 MIN_HEIGHT,
54 NAT_WIDTH,
55 NAT_HEIGHT,
56 MAX_WIDTH,
57 MAX_HEIGHT,
58 LAST_VALUE
59} GuideValue;
60
61struct _GtkConstraintGuide
62{
63 GObject parent_instance;
64
65 char *name;
66
67 int strength;
68
69 int values[LAST_VALUE];
70
71 GtkConstraintLayout *layout;
72
73 /* HashTable<static string, Variable>; a hash table of variables,
74 * one for each attribute; we use these to query and suggest the
75 * values for the solver. The string is static and does not need
76 * to be freed.
77 */
78 GHashTable *bound_attributes;
79
80 GtkConstraintRef *constraints[LAST_VALUE];
81};
82
83
84struct _GtkConstraintGuideClass {
85 GObjectClass parent_class;
86};
87
88enum {
89 PROP_MIN_WIDTH = 1,
90 PROP_MIN_HEIGHT,
91 PROP_NAT_WIDTH,
92 PROP_NAT_HEIGHT,
93 PROP_MAX_WIDTH,
94 PROP_MAX_HEIGHT,
95 PROP_STRENGTH,
96 PROP_NAME,
97 LAST_PROP
98};
99
100static GParamSpec *guide_props[LAST_PROP];
101
102static void
103gtk_constraint_guide_constraint_target_iface_init (GtkConstraintTargetInterface *iface)
104{
105}
106
107G_DEFINE_TYPE_WITH_CODE (GtkConstraintGuide, gtk_constraint_guide, G_TYPE_OBJECT,
108 G_IMPLEMENT_INTERFACE (GTK_TYPE_CONSTRAINT_TARGET,
109 gtk_constraint_guide_constraint_target_iface_init))
110
111static void
112gtk_constraint_guide_init (GtkConstraintGuide *guide)
113{
114 guide->strength = GTK_CONSTRAINT_STRENGTH_MEDIUM;
115
116 guide->values[MIN_WIDTH] = 0;
117 guide->values[MIN_HEIGHT] = 0;
118 guide->values[NAT_WIDTH] = 0;
119 guide->values[NAT_HEIGHT] = 0;
120 guide->values[MAX_WIDTH] = G_MAXINT;
121 guide->values[MAX_HEIGHT] = G_MAXINT;
122
123 guide->bound_attributes =
124 g_hash_table_new_full (hash_func: g_str_hash, key_equal_func: g_str_equal,
125 NULL,
126 value_destroy_func: (GDestroyNotify) gtk_constraint_variable_unref);
127}
128
129static void
130gtk_constraint_guide_update_constraint (GtkConstraintGuide *guide,
131 GuideValue index)
132{
133 GtkConstraintSolver *solver;
134 GtkConstraintVariable *var;
135
136 if (!guide->layout)
137 return;
138
139 solver = gtk_constraint_layout_get_solver (layout: guide->layout);
140 if (!solver)
141 return;
142
143 if (guide->constraints[index] != NULL)
144 {
145 gtk_constraint_solver_remove_constraint (solver, reference: guide->constraints[index]);
146 guide->constraints[index] = NULL;
147 }
148
149 if (index == MIN_WIDTH || index == NAT_WIDTH || index == MAX_WIDTH)
150 var = gtk_constraint_layout_get_attribute (layout: guide->layout, attr: GTK_CONSTRAINT_ATTRIBUTE_WIDTH, prefix: "guide", NULL, bound_attributes: guide->bound_attributes);
151 else
152 var = gtk_constraint_layout_get_attribute (layout: guide->layout, attr: GTK_CONSTRAINT_ATTRIBUTE_HEIGHT, prefix: "guide", NULL, bound_attributes: guide->bound_attributes);
153
154 /* We always install min-size constraints,
155 * but we avoid nat-size constraints if min == max
156 * and we avoid max-size constraints if max == G_MAXINT
157 */
158 if (index == MIN_WIDTH || index == MIN_HEIGHT)
159 {
160 guide->constraints[index] =
161 gtk_constraint_solver_add_constraint (solver,
162 variable: var,
163 relation: GTK_CONSTRAINT_RELATION_GE,
164 expression: gtk_constraint_expression_new (constant: guide->values[index]),
165 strength: GTK_CONSTRAINT_STRENGTH_REQUIRED);
166 }
167 else if ((index == NAT_WIDTH && guide->values[MIN_WIDTH] != guide->values[MAX_WIDTH]) ||
168 (index == NAT_HEIGHT && guide->values[MIN_HEIGHT] != guide->values[MAX_HEIGHT]))
169 {
170 gtk_constraint_variable_set_value (variable: var, value: guide->values[index]);
171 guide->constraints[index] =
172 gtk_constraint_solver_add_stay_variable (solver,
173 variable: var,
174 strength: guide->strength);
175 }
176 else if ((index == MAX_WIDTH || index == MAX_HEIGHT) &&
177 guide->values[index] < G_MAXINT)
178 {
179 guide->constraints[index] =
180 gtk_constraint_solver_add_constraint (solver,
181 variable: var,
182 relation: GTK_CONSTRAINT_RELATION_LE,
183 expression: gtk_constraint_expression_new (constant: guide->values[index]),
184 strength: GTK_CONSTRAINT_STRENGTH_REQUIRED);
185 }
186
187 gtk_layout_manager_layout_changed (manager: GTK_LAYOUT_MANAGER (ptr: guide->layout));
188}
189
190void
191gtk_constraint_guide_update (GtkConstraintGuide *guide)
192{
193 int i;
194
195 for (i = 0; i < LAST_VALUE; i++)
196 gtk_constraint_guide_update_constraint (guide, index: i);
197}
198
199void
200gtk_constraint_guide_detach (GtkConstraintGuide *guide)
201{
202 GtkConstraintSolver *solver;
203 int i;
204
205 if (!guide->layout)
206 return;
207
208 solver = gtk_constraint_layout_get_solver (layout: guide->layout);
209 if (!solver)
210 return;
211
212 for (i = 0; i < LAST_VALUE; i++)
213 {
214 if (guide->constraints[i])
215 {
216 gtk_constraint_solver_remove_constraint (solver, reference: guide->constraints[i]);
217 guide->constraints[i] = NULL;
218 }
219 }
220
221 g_hash_table_remove_all (hash_table: guide->bound_attributes);
222}
223
224GtkConstraintVariable *
225gtk_constraint_guide_get_attribute (GtkConstraintGuide *guide,
226 GtkConstraintAttribute attr)
227{
228 GtkLayoutManager *manager = GTK_LAYOUT_MANAGER (ptr: guide->layout);
229 GtkWidget *widget = gtk_layout_manager_get_widget (manager);
230
231 return gtk_constraint_layout_get_attribute (layout: guide->layout, attr, prefix: "guide", widget, bound_attributes: guide->bound_attributes);
232}
233
234GtkConstraintLayout *
235gtk_constraint_guide_get_layout (GtkConstraintGuide *guide)
236{
237 return guide->layout;
238}
239
240void
241gtk_constraint_guide_set_layout (GtkConstraintGuide *guide,
242 GtkConstraintLayout *layout)
243{
244 guide->layout = layout;
245}
246
247static void
248gtk_constraint_guide_set_property (GObject *gobject,
249 guint prop_id,
250 const GValue *value,
251 GParamSpec *pspec)
252{
253 GtkConstraintGuide *self = GTK_CONSTRAINT_GUIDE (ptr: gobject);
254 int val;
255 GuideValue index;
256
257 switch (prop_id)
258 {
259 case PROP_MIN_WIDTH:
260 case PROP_MIN_HEIGHT:
261 case PROP_NAT_WIDTH:
262 case PROP_NAT_HEIGHT:
263 case PROP_MAX_WIDTH:
264 case PROP_MAX_HEIGHT:
265 val = g_value_get_int (value);
266 index = prop_id - 1;
267 if (self->values[index] != val)
268 {
269 self->values[index] = val;
270 g_object_notify_by_pspec (object: gobject, pspec);
271
272 gtk_constraint_guide_update_constraint (guide: self, index);
273 if (index == MIN_WIDTH || index == MAX_WIDTH)
274 gtk_constraint_guide_update_constraint (guide: self, index: NAT_WIDTH);
275 if (index == MIN_HEIGHT || index == MAX_HEIGHT)
276 gtk_constraint_guide_update_constraint (guide: self, index: NAT_HEIGHT);
277 }
278 break;
279
280 case PROP_STRENGTH:
281 gtk_constraint_guide_set_strength (guide: self, strength: g_value_get_enum (value));
282 break;
283
284 case PROP_NAME:
285 gtk_constraint_guide_set_name (guide: self, name: g_value_get_string (value));
286 break;
287
288 default:
289 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
290 break;
291 }
292}
293
294static void
295gtk_constraint_guide_get_property (GObject *gobject,
296 guint prop_id,
297 GValue *value,
298 GParamSpec *pspec)
299{
300 GtkConstraintGuide *self = GTK_CONSTRAINT_GUIDE (ptr: gobject);
301
302 switch (prop_id)
303 {
304 case PROP_MIN_WIDTH:
305 case PROP_MIN_HEIGHT:
306 case PROP_NAT_WIDTH:
307 case PROP_NAT_HEIGHT:
308 case PROP_MAX_WIDTH:
309 case PROP_MAX_HEIGHT:
310 g_value_set_int (value, v_int: self->values[prop_id - 1]);
311 break;
312
313 case PROP_STRENGTH:
314 g_value_set_enum (value, v_enum: self->strength);
315 break;
316
317 case PROP_NAME:
318 g_value_set_string (value, v_string: self->name);
319 break;
320
321 default:
322 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
323 break;
324 }
325}
326
327static void
328gtk_constraint_guide_finalize (GObject *object)
329{
330 GtkConstraintGuide *self = GTK_CONSTRAINT_GUIDE (ptr: object);
331
332 g_free (mem: self->name);
333
334 g_clear_pointer (&self->bound_attributes, g_hash_table_unref);
335
336 G_OBJECT_CLASS (gtk_constraint_guide_parent_class)->finalize (object);
337}
338
339static void
340gtk_constraint_guide_class_init (GtkConstraintGuideClass *class)
341{
342 GObjectClass *object_class = G_OBJECT_CLASS (class);
343
344 object_class->finalize = gtk_constraint_guide_finalize;
345 object_class->set_property = gtk_constraint_guide_set_property;
346 object_class->get_property = gtk_constraint_guide_get_property;
347
348 /**
349 * GtkConstraintGuide:min-width:
350 *
351 * The minimum width of the guide.
352 */
353 guide_props[PROP_MIN_WIDTH] =
354 g_param_spec_int (name: "min-width",
355 nick: "Minimum width",
356 blurb: "Minimum width",
357 minimum: 0, G_MAXINT, default_value: 0,
358 flags: G_PARAM_READWRITE|
359 G_PARAM_EXPLICIT_NOTIFY);
360
361 /**
362 * GtkConstraintGuide:min-height:
363 *
364 * The minimum height of the guide.
365 */
366 guide_props[PROP_MIN_HEIGHT] =
367 g_param_spec_int (name: "min-height",
368 nick: "Minimum height",
369 blurb: "Minimum height",
370 minimum: 0, G_MAXINT, default_value: 0,
371 flags: G_PARAM_READWRITE|
372 G_PARAM_EXPLICIT_NOTIFY);
373
374 /**
375 * GtkConstraintGuide:nat-width:
376 *
377 * The preferred, or natural, width of the guide.
378 */
379 guide_props[PROP_NAT_WIDTH] =
380 g_param_spec_int (name: "nat-width",
381 nick: "Natural width",
382 blurb: "Natural width",
383 minimum: 0, G_MAXINT, default_value: 0,
384 flags: G_PARAM_READWRITE|
385 G_PARAM_EXPLICIT_NOTIFY);
386
387 /**
388 * GtkConstraintGuide:nat-height:
389 *
390 * The preferred, or natural, height of the guide.
391 */
392 guide_props[PROP_NAT_HEIGHT] =
393 g_param_spec_int (name: "nat-height",
394 nick: "Natural height",
395 blurb: "Natural height",
396 minimum: 0, G_MAXINT, default_value: 0,
397 flags: G_PARAM_READWRITE|
398 G_PARAM_EXPLICIT_NOTIFY);
399
400 /**
401 * GtkConstraintGuide:max-width:
402 *
403 * The maximum width of the guide.
404 */
405 guide_props[PROP_MAX_WIDTH] =
406 g_param_spec_int (name: "max-width",
407 nick: "Maximum width",
408 blurb: "Maximum width",
409 minimum: 0, G_MAXINT, G_MAXINT,
410 flags: G_PARAM_READWRITE|
411 G_PARAM_EXPLICIT_NOTIFY);
412
413 /**
414 * GtkConstraintGuide:max-height:
415 *
416 * The maximum height of the guide.
417 */
418 guide_props[PROP_MAX_HEIGHT] =
419 g_param_spec_int (name: "max-height",
420 nick: "Maximum height",
421 blurb: "Maximum height",
422 minimum: 0, G_MAXINT, G_MAXINT,
423 flags: G_PARAM_READWRITE|
424 G_PARAM_EXPLICIT_NOTIFY);
425
426 /**
427 * GtkConstraintGuide:strength: (attributes org.gtk.Property.get=gtk_constraint_guide_get_strength org.gtk.Property.set=gtk_constraint_guide_set_strength)
428 *
429 * The `GtkConstraintStrength` to be used for the constraint on
430 * the natural size of the guide.
431 */
432 guide_props[PROP_STRENGTH] =
433 g_param_spec_enum (name: "strength",
434 nick: "Strength",
435 blurb: "The strength to use for natural size",
436 enum_type: GTK_TYPE_CONSTRAINT_STRENGTH,
437 default_value: GTK_CONSTRAINT_STRENGTH_MEDIUM,
438 flags: G_PARAM_READWRITE|
439 G_PARAM_EXPLICIT_NOTIFY);
440
441 /**
442 * GtkConstraintGuide:name: (attributes org.gtk.Property.get=gtk_constraint_guide_get_name org.gtk.Property.set=gtk_constraint_guide_set_name)
443 *
444 * A name that identifies the `GtkConstraintGuide`, for debugging.
445 */
446 guide_props[PROP_NAME] =
447 g_param_spec_string (name: "name",
448 nick: "Name",
449 blurb: "A name to use in debug message",
450 NULL,
451 flags: G_PARAM_READWRITE);
452
453 g_object_class_install_properties (oclass: object_class, n_pspecs: LAST_PROP, pspecs: guide_props);
454}
455
456/**
457 * gtk_constraint_guide_new:
458 *
459 * Creates a new `GtkConstraintGuide` object.
460 *
461 * Return: a new `GtkConstraintGuide` object.
462 */
463GtkConstraintGuide *
464gtk_constraint_guide_new (void)
465{
466 return g_object_new (GTK_TYPE_CONSTRAINT_GUIDE, NULL);
467}
468
469/**
470 * gtk_constraint_guide_set_min_size:
471 * @guide: a `GtkConstraintGuide` object
472 * @width: the new minimum width, or -1 to not change it
473 * @height: the new minimum height, or -1 to not change it
474 *
475 * Sets the minimum size of @guide.
476 *
477 * If @guide is attached to a `GtkConstraintLayout`,
478 * the constraints will be updated to reflect the new size.
479 */
480void
481gtk_constraint_guide_set_min_size (GtkConstraintGuide *guide,
482 int width,
483 int height)
484{
485 g_return_if_fail (GTK_IS_CONSTRAINT_GUIDE (guide));
486 g_return_if_fail (width >= -1);
487 g_return_if_fail (height >= -1);
488
489 g_object_freeze_notify (G_OBJECT (guide));
490
491 if (width != -1)
492 g_object_set (object: guide, first_property_name: "min-width", width, NULL);
493
494 if (height != -1)
495 g_object_set (object: guide, first_property_name: "min-height", height, NULL);
496
497 g_object_thaw_notify (G_OBJECT (guide));
498}
499
500/**
501 * gtk_constraint_guide_get_min_size:
502 * @guide: a `GtkConstraintGuide` object
503 * @width: (out) (optional): return location for the minimum width
504 * @height: (out) (optional): return location for the minimum height
505 *
506 * Gets the minimum size of @guide.
507 */
508void
509gtk_constraint_guide_get_min_size (GtkConstraintGuide *guide,
510 int *width,
511 int *height)
512{
513 g_return_if_fail (GTK_IS_CONSTRAINT_GUIDE (guide));
514
515 if (width)
516 *width = guide->values[MIN_WIDTH];
517 if (height)
518 *height = guide->values[MIN_HEIGHT];
519}
520
521/**
522 * gtk_constraint_guide_set_nat_size:
523 * @guide: a `GtkConstraintGuide` object
524 * @width: the new natural width, or -1 to not change it
525 * @height: the new natural height, or -1 to not change it
526 *
527 * Sets the natural size of @guide.
528 *
529 * If @guide is attached to a `GtkConstraintLayout`,
530 * the constraints will be updated to reflect the new size.
531 */
532void
533gtk_constraint_guide_set_nat_size (GtkConstraintGuide *guide,
534 int width,
535 int height)
536{
537 g_return_if_fail (GTK_IS_CONSTRAINT_GUIDE (guide));
538 g_return_if_fail (width >= -1);
539 g_return_if_fail (height >= -1);
540
541 g_object_freeze_notify (G_OBJECT (guide));
542
543 if (width != -1)
544 g_object_set (object: guide, first_property_name: "nat-width", width, NULL);
545
546 if (height != -1)
547 g_object_set (object: guide, first_property_name: "nat-height", height, NULL);
548
549 g_object_thaw_notify (G_OBJECT (guide));
550}
551
552/**
553 * gtk_constraint_guide_get_nat_size:
554 * @guide: a `GtkConstraintGuide` object
555 * @width: (out) (optional): return location for the natural width
556 * @height: (out) (optional): return location for the natural height
557 *
558 * Gets the natural size of @guide.
559 */
560void
561gtk_constraint_guide_get_nat_size (GtkConstraintGuide *guide,
562 int *width,
563 int *height)
564{
565 g_return_if_fail (GTK_IS_CONSTRAINT_GUIDE (guide));
566
567 if (width)
568 *width = guide->values[NAT_WIDTH];
569 if (height)
570 *height = guide->values[NAT_HEIGHT];
571}
572
573/**
574 * gtk_constraint_guide_set_max_size:
575 * @guide: a `GtkConstraintGuide` object
576 * @width: the new maximum width, or -1 to not change it
577 * @height: the new maximum height, or -1 to not change it
578 *
579 * Sets the maximum size of @guide.
580 *
581 * If @guide is attached to a `GtkConstraintLayout`,
582 * the constraints will be updated to reflect the new size.
583 */
584void
585gtk_constraint_guide_set_max_size (GtkConstraintGuide *guide,
586 int width,
587 int height)
588{
589 g_return_if_fail (GTK_IS_CONSTRAINT_GUIDE (guide));
590 g_return_if_fail (width >= -1);
591 g_return_if_fail (height >= -1);
592
593 g_object_freeze_notify (G_OBJECT (guide));
594
595 if (width != -1)
596 g_object_set (object: guide, first_property_name: "max-width", width, NULL);
597
598 if (height != -1)
599 g_object_set (object: guide, first_property_name: "max-height", height, NULL);
600
601 g_object_thaw_notify (G_OBJECT (guide));
602}
603
604/**
605 * gtk_constraint_guide_get_max_size:
606 * @guide: a `GtkConstraintGuide` object
607 * @width: (out) (optional): return location for the maximum width
608 * @height: (out) (optional): return location for the maximum height
609 *
610 * Gets the maximum size of @guide.
611 */
612void
613gtk_constraint_guide_get_max_size (GtkConstraintGuide *guide,
614 int *width,
615 int *height)
616{
617 g_return_if_fail (GTK_IS_CONSTRAINT_GUIDE (guide));
618
619 if (width)
620 *width = guide->values[MAX_WIDTH];
621 if (height)
622 *height = guide->values[MAX_HEIGHT];
623}
624
625/**
626 * gtk_constraint_guide_get_name: (attributes org.gtk.Method.get_property=name)
627 * @guide: a `GtkConstraintGuide`
628 *
629 * Retrieves the name set using gtk_constraint_guide_set_name().
630 *
631 * Returns: (transfer none) (nullable): the name of the guide
632 */
633const char *
634gtk_constraint_guide_get_name (GtkConstraintGuide *guide)
635{
636 g_return_val_if_fail (GTK_IS_CONSTRAINT_GUIDE (guide), NULL);
637
638 return guide->name;
639}
640
641/**
642 * gtk_constraint_guide_set_name: (attributes org.gtk.Method.set_property=name)
643 * @guide: a `GtkConstraintGuide`
644 * @name: (nullable): a name for the @guide
645 *
646 * Sets a name for the given `GtkConstraintGuide`.
647 *
648 * The name is useful for debugging purposes.
649 */
650void
651gtk_constraint_guide_set_name (GtkConstraintGuide *guide,
652 const char *name)
653{
654 g_return_if_fail (GTK_IS_CONSTRAINT_GUIDE (guide));
655
656 g_free (mem: guide->name);
657 guide->name = g_strdup (str: name);
658 g_object_notify_by_pspec (G_OBJECT (guide), pspec: guide_props[PROP_NAME]);
659}
660
661/**
662 * gtk_constraint_guide_get_strength: (attributes org.gtk.Method.get_property=strength)
663 * @guide: a `GtkConstraintGuide`
664 *
665 * Retrieves the strength set using gtk_constraint_guide_set_strength().
666 *
667 * Returns: the strength of the constraint on the natural size
668 */
669GtkConstraintStrength
670gtk_constraint_guide_get_strength (GtkConstraintGuide *guide)
671{
672 g_return_val_if_fail (GTK_IS_CONSTRAINT_GUIDE (guide),
673 GTK_CONSTRAINT_STRENGTH_MEDIUM);
674
675 return guide->strength;
676}
677
678/**
679 * gtk_constraint_guide_set_strength: (attributes org.gtk.Method.set_property=strength)
680 * @guide: a `GtkConstraintGuide`
681 * @strength: the strength of the constraint
682 *
683 * Sets the strength of the constraint on the natural size of the
684 * given `GtkConstraintGuide`.
685 */
686void
687gtk_constraint_guide_set_strength (GtkConstraintGuide *guide,
688 GtkConstraintStrength strength)
689{
690 g_return_if_fail (GTK_IS_CONSTRAINT_GUIDE (guide));
691
692 if (guide->strength == strength)
693 return;
694
695 guide->strength = strength;
696 g_object_notify_by_pspec (G_OBJECT (guide), pspec: guide_props[PROP_STRENGTH]);
697 gtk_constraint_guide_update_constraint (guide, index: NAT_WIDTH);
698 gtk_constraint_guide_update_constraint (guide, index: NAT_HEIGHT);
699}
700

source code of gtk/gtk/gtkconstraintguide.c