/* gtkconstraintlayout.c: Layout manager using constraints
* Copyright 2019 GNOME Foundation
*
* This library is free software ; you can redistribute it and / or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation ; either
* version 2 . 1 of the License , or ( at your option ) any later version .
*
* This library is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
* Lesser General Public License for more details .
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library . If not , see < http : //www.gnu.org/licenses/>.
*
* Author : Emmanuele Bassi
*/
/**
* GtkConstraintLayout :
*
* Uses constraints to describe relations between widgets .
*
* ` GtkConstraintLayout ` is a layout manager that uses relations between
* widget attributes , expressed via [ class @ Gtk . Constraint ] instances , to
* measure and allocate widgets .
*
* # # # How do constraints work
*
* Constraints are objects defining the relationship between attributes
* of a widget ; you can read the description of the [ class @ Gtk . Constraint ]
* class to have a more in depth definition .
*
* By taking multiple constraints and applying them to the children of
* a widget using ` GtkConstraintLayout ` , it ' s possible to describe
* complex layout policies ; each constraint applied to a child or to the parent
* widgets contributes to the full description of the layout , in terms of
* parameters for resolving the value of each attribute .
*
* It is important to note that a layout is defined by the totality of
* constraints ; removing a child , or a constraint , from an existing layout
* without changing the remaining constraints may result in an unstable
* or unsolvable layout .
*
* Constraints have an implicit " reading order " ; you should start describing
* each edge of each child , as well as their relationship with the parent
* container , from the top left ( or top right , in RTL languages ) , horizontally
* first , and then vertically .
*
* A constraint - based layout with too few constraints can become " unstable " ,
* that is : have more than one solution . The behavior of an unstable layout
* is undefined .
*
* A constraint - based layout with conflicting constraints may be unsolvable ,
* and lead to an unstable layout . You can use the [ property @ Gtk . Constraint : strength ]
* property of [ class @ Gtk . Constraint ] to " nudge " the layout towards a solution .
*
* # # # GtkConstraintLayout as GtkBuildable
*
* ` GtkConstraintLayout ` implements the [ iface @ Gtk . Buildable ] interface and
* has a custom " constraints " element which allows describing constraints in
* a [ class @ Gtk . Builder ] UI file .
*
* An example of a UI definition fragment specifying a constraint :
*
* ` ` ` xml
* < object class = " GtkConstraintLayout " >
* < constraints >
* < constraint target = " button " target - attribute = " start "
* relation = " eq "
* source = " super " source - attribute = " start "
* constant = " 12 "
* strength = " required " / >
* < constraint target = " button " target - attribute = " width "
* relation = " ge "
* constant = " 250 "
* strength = " strong " / >
* < / constraints >
* < / object >
* ` ` `
*
* The definition above will add two constraints to the GtkConstraintLayout :
*
* - a required constraint between the leading edge of " button " and
* the leading edge of the widget using the constraint layout , plus
* 12 pixels
* - a strong , constant constraint making the width of " button " greater
* than , or equal to 250 pixels
*
* The " target " and " target - attribute " attributes are required .
*
* The " source " and " source - attribute " attributes of the " constraint "
* element are optional ; if they are not specified , the constraint is
* assumed to be a constant .
*
* The " relation " attribute is optional ; if not specified , the constraint
* is assumed to be an equality .
*
* The " strength " attribute is optional ; if not specified , the constraint
* is assumed to be required .
*
* The " source " and " target " attributes can be set to " super " to indicate
* that the constraint target is the widget using the GtkConstraintLayout .
*
* There can be " constant " and " multiplier " attributes .
*
* Additionally , the " constraints " element can also contain a description
* of the ` GtkConstraintGuides ` used by the layout :
*
* ` ` ` xml
* < constraints >
* < guide min - width = " 100 " max - width = " 500 " name = " hspace " / >
* < guide min - height = " 64 " nat - height = " 128 " name = " vspace " strength = " strong " / >
* < / constraints >
* ` ` `
*
* The " guide " element has the following optional attributes :
*
* - " min - width " , " nat - width " , and " max - width " , describe the minimum ,
* natural , and maximum width of the guide , respectively
* - " min - height " , " nat - height " , and " max - height " , describe the minimum ,
* natural , and maximum height of the guide , respectively
* - " strength " describes the strength of the constraint on the natural
* size of the guide ; if not specified , the constraint is assumed to
* have a medium strength
* - " name " describes a name for the guide , useful when debugging
*
* # # # Using the Visual Format Language
*
* Complex constraints can be described using a compact syntax called VFL ,
* or * Visual Format Language * .
*
* The Visual Format Language describes all the constraints on a row or
* column , typically starting from the leading edge towards the trailing
* one . Each element of the layout is composed by " views " , which identify
* a [ iface @ Gtk . ConstraintTarget ] .
*
* For instance :
*
* ` ` `
* [ button ] - [ textField ]
* ` ` `
*
* Describes a constraint that binds the trailing edge of " button " to the
* leading edge of " textField " , leaving a default space between the two .
*
* Using VFL is also possible to specify predicates that describe constraints
* on attributes like width and height :
*
* ` ` `
* // Width must be greater than, or equal to 50
* [ button ( > = 50 ) ]
*
* // Width of button1 must be equal to width of button2
* [ button1 ( = = button2 ) ]
* ` ` `
*
* The default orientation for a VFL description is horizontal , unless
* otherwise specified :
*
* ` ` `
* // horizontal orientation, default attribute: width
* H : [ button ( > = 150 ) ]
*
* // vertical orientation, default attribute: height
* V : [ button1 ( = = button2 ) ]
* ` ` `
*
* It ' s also possible to specify multiple predicates , as well as their
* strength :
*
* ` ` `
* // minimum width of button must be 150
* // natural width of button can be 250
* [ button ( > = 150 @ required , = = 250 @ medium ) ]
* ` ` `
*
* Finally , it ' s also possible to use simple arithmetic operators :
*
* ` ` `
* // width of button1 must be equal to width of button2
* // divided by 2 plus 12
* [ button1 ( button2 / 2 + 12 ) ]
* ` ` `
*/
/**
* GtkConstraintLayoutChild :
*
* ` GtkLayoutChild ` subclass for children in a ` GtkConstraintLayout ` .
*/
#include "config.h"
#include "gtkconstraintlayout.h"
#include "gtkconstraintlayoutprivate.h"
#include "gtkconstraintprivate.h"
#include "gtkconstraintexpressionprivate.h"
#include "gtkconstraintguideprivate.h"
#include "gtkconstraintsolverprivate.h"
#include "gtkconstraintvflparserprivate.h"
#include "gtkbuildable.h"
#include "gtkbuilderprivate.h"
#include "gtkdebug.h"
#include "gtklayoutchild.h"
#include "gtkprivate.h"
#include "gtksizerequest.h"
#include "gtkwidgetprivate.h"
#include <string.h>
#include <errno.h>
enum {
MIN_WIDTH,
MIN_HEIGHT,
NAT_WIDTH,
NAT_HEIGHT,
LAST_VALUE
};
struct _GtkConstraintLayoutChild
{
GtkLayoutChild parent_instance;
int values[LAST_VALUE];
GtkConstraintRef *constraints[LAST_VALUE];
/* HashTable<static string, Variable>; a hash table of variables,
* one for each attribute ; we use these to query and suggest the
* values for the solver . The string is static and does not need
* to be freed .
*/
GHashTable *bound_attributes;
};
struct _GtkConstraintLayout
{
GtkLayoutManager parent_instance;
/* A pointer to the GtkConstraintSolver used by the layout manager;
* we acquire one when the layout manager gets rooted , and release
* it when it gets unrooted .
*/
GtkConstraintSolver *solver;
/* HashTable<static string, Variable>; a hash table of variables,
* one for each attribute ; we use these to query and suggest the
* values for the solver . The string is static and does not need
* to be freed .
*/
GHashTable *bound_attributes;
/* HashSet<GtkConstraint>; the set of constraints on the
* parent widget , using the public API objects .
*/
GHashTable *constraints;
/* HashSet<GtkConstraintGuide> */
GHashTable *guides;
GListStore *constraints_observer;
GListStore *guides_observer;
};
G_DEFINE_TYPE (GtkConstraintLayoutChild, gtk_constraint_layout_child, GTK_TYPE_LAYOUT_CHILD)
GtkConstraintSolver *
gtk_constraint_layout_get_solver (GtkConstraintLayout *self)
{
GtkWidget *widget;
GtkRoot *root;
if (self->solver != NULL)
return self->solver;
widget = gtk_layout_manager_get_widget (GTK_LAYOUT_MANAGER (self));
if (widget == NULL)
return NULL;
root = gtk_widget_get_root (widget);
if (root == NULL)
return NULL;
self->solver = gtk_root_get_constraint_solver (root);
return self->solver;
}
static const char * const attribute_names[] = {
[GTK_CONSTRAINT_ATTRIBUTE_NONE] = "none" ,
[GTK_CONSTRAINT_ATTRIBUTE_LEFT] = "left" ,
[GTK_CONSTRAINT_ATTRIBUTE_RIGHT] = "right" ,
[GTK_CONSTRAINT_ATTRIBUTE_TOP] = "top" ,
[GTK_CONSTRAINT_ATTRIBUTE_BOTTOM] = "bottom" ,
[GTK_CONSTRAINT_ATTRIBUTE_START] = "start" ,
[GTK_CONSTRAINT_ATTRIBUTE_END] = "end" ,
[GTK_CONSTRAINT_ATTRIBUTE_WIDTH] = "width" ,
[GTK_CONSTRAINT_ATTRIBUTE_HEIGHT] = "height" ,
[GTK_CONSTRAINT_ATTRIBUTE_CENTER_X] = "center-x" ,
[GTK_CONSTRAINT_ATTRIBUTE_CENTER_Y] = "center-y" ,
[GTK_CONSTRAINT_ATTRIBUTE_BASELINE] = "baseline" ,
};
G_GNUC_PURE
static const char *
get_attribute_name (GtkConstraintAttribute attr)
{
return attribute_names[attr];
}
static GtkConstraintAttribute
resolve_direction (GtkConstraintAttribute attr,
GtkWidget *widget)
{
GtkTextDirection text_dir;
/* Resolve the start/end attributes depending on the layout's text direction */
if (widget)
text_dir = gtk_widget_get_direction (widget);
else
text_dir = GTK_TEXT_DIR_LTR;
if (attr == GTK_CONSTRAINT_ATTRIBUTE_START)
{
if (text_dir == GTK_TEXT_DIR_RTL)
attr = GTK_CONSTRAINT_ATTRIBUTE_RIGHT;
else
attr = GTK_CONSTRAINT_ATTRIBUTE_LEFT;
}
else if (attr == GTK_CONSTRAINT_ATTRIBUTE_END)
{
if (text_dir == GTK_TEXT_DIR_RTL)
attr = GTK_CONSTRAINT_ATTRIBUTE_LEFT;
else
attr = GTK_CONSTRAINT_ATTRIBUTE_RIGHT;
}
return attr;
}
GtkConstraintVariable *
gtk_constraint_layout_get_attribute (GtkConstraintLayout *layout,
GtkConstraintAttribute attr,
const char *prefix,
GtkWidget *widget,
GHashTable *bound_attributes)
{
const char *attr_name;
GtkConstraintVariable *res;
GtkConstraintSolver *solver = layout->solver;
attr = resolve_direction (attr, widget);
attr_name = get_attribute_name (attr);
res = g_hash_table_lookup (bound_attributes, attr_name);
if (res != NULL)
return res;
res = gtk_constraint_solver_create_variable (solver, prefix, attr_name, 0 .0 );
g_hash_table_insert (bound_attributes, (gpointer) attr_name, res);
/* Some attributes are really constraints computed from other
* attributes , to avoid creating additional constraints from
* the user ' s perspective
*/
switch (attr)
{
/* right = left + width */
case GTK_CONSTRAINT_ATTRIBUTE_RIGHT:
{
GtkConstraintExpressionBuilder builder;
GtkConstraintVariable *left, *width;
GtkConstraintExpression *expr;
left = gtk_constraint_layout_get_attribute (layout, GTK_CONSTRAINT_ATTRIBUTE_LEFT, prefix, widget, bound_attributes);
width = gtk_constraint_layout_get_attribute (layout, GTK_CONSTRAINT_ATTRIBUTE_WIDTH, prefix, widget, bound_attributes);
gtk_constraint_expression_builder_init (&builder, solver);
gtk_constraint_expression_builder_term (&builder, left);
gtk_constraint_expression_builder_plus (&builder);
gtk_constraint_expression_builder_term (&builder, width);
expr = gtk_constraint_expression_builder_finish (&builder);
gtk_constraint_solver_add_constraint (solver,
res, GTK_CONSTRAINT_RELATION_EQ, expr,
GTK_CONSTRAINT_STRENGTH_REQUIRED);
}
break ;
/* bottom = top + height */
case GTK_CONSTRAINT_ATTRIBUTE_BOTTOM:
{
GtkConstraintExpressionBuilder builder;
GtkConstraintVariable *top, *height;
GtkConstraintExpression *expr;
top = gtk_constraint_layout_get_attribute (layout, GTK_CONSTRAINT_ATTRIBUTE_TOP, prefix, widget, bound_attributes);
height = gtk_constraint_layout_get_attribute (layout, GTK_CONSTRAINT_ATTRIBUTE_HEIGHT, prefix, widget, bound_attributes);
gtk_constraint_expression_builder_init (&builder, solver);
gtk_constraint_expression_builder_term (&builder, top);
gtk_constraint_expression_builder_plus (&builder);
gtk_constraint_expression_builder_term (&builder, height);
expr = gtk_constraint_expression_builder_finish (&builder);
gtk_constraint_solver_add_constraint (solver,
res, GTK_CONSTRAINT_RELATION_EQ, expr,
GTK_CONSTRAINT_STRENGTH_REQUIRED);
}
break ;
/* centerX = (width / 2.0) + left*/
case GTK_CONSTRAINT_ATTRIBUTE_CENTER_X:
{
GtkConstraintExpressionBuilder builder;
GtkConstraintVariable *left, *width;
GtkConstraintExpression *expr;
left = gtk_constraint_layout_get_attribute (layout, GTK_CONSTRAINT_ATTRIBUTE_LEFT, prefix, widget, bound_attributes);
width = gtk_constraint_layout_get_attribute (layout, GTK_CONSTRAINT_ATTRIBUTE_WIDTH, prefix, widget, bound_attributes);
gtk_constraint_expression_builder_init (&builder, solver);
gtk_constraint_expression_builder_term (&builder, width);
gtk_constraint_expression_builder_divide_by (&builder);
gtk_constraint_expression_builder_constant (&builder, 2 .0 );
gtk_constraint_expression_builder_plus (&builder);
gtk_constraint_expression_builder_term (&builder, left);
expr = gtk_constraint_expression_builder_finish (&builder);
gtk_constraint_solver_add_constraint (solver,
res, GTK_CONSTRAINT_RELATION_EQ, expr,
GTK_CONSTRAINT_STRENGTH_REQUIRED);
}
break ;
/* centerY = (height / 2.0) + top */
case GTK_CONSTRAINT_ATTRIBUTE_CENTER_Y:
{
GtkConstraintExpressionBuilder builder;
GtkConstraintVariable *top, *height;
GtkConstraintExpression *expr;
top = gtk_constraint_layout_get_attribute (layout, GTK_CONSTRAINT_ATTRIBUTE_TOP, prefix, widget, bound_attributes);
height = gtk_constraint_layout_get_attribute (layout, GTK_CONSTRAINT_ATTRIBUTE_HEIGHT, prefix, widget, bound_attributes);
gtk_constraint_expression_builder_init (&builder, solver);
gtk_constraint_expression_builder_term (&builder, height);
gtk_constraint_expression_builder_divide_by (&builder);
gtk_constraint_expression_builder_constant (&builder, 2 .0 );
gtk_constraint_expression_builder_plus (&builder);
gtk_constraint_expression_builder_term (&builder, top);
expr = gtk_constraint_expression_builder_finish (&builder);
gtk_constraint_solver_add_constraint (solver,
res, GTK_CONSTRAINT_RELATION_EQ, expr,
GTK_CONSTRAINT_STRENGTH_REQUIRED);
}
break ;
/* We do not allow negative sizes */
case GTK_CONSTRAINT_ATTRIBUTE_WIDTH:
case GTK_CONSTRAINT_ATTRIBUTE_HEIGHT:
{
GtkConstraintExpression *expr;
expr = gtk_constraint_expression_new (0 .0 );
gtk_constraint_solver_add_constraint (solver,
res, GTK_CONSTRAINT_RELATION_GE, expr,
GTK_CONSTRAINT_STRENGTH_REQUIRED);
}
break ;
/* These are "pure" attributes */
case GTK_CONSTRAINT_ATTRIBUTE_NONE:
case GTK_CONSTRAINT_ATTRIBUTE_LEFT:
case GTK_CONSTRAINT_ATTRIBUTE_TOP:
case GTK_CONSTRAINT_ATTRIBUTE_BASELINE:
break ;
/* These attributes must have been resolved to their real names */
case GTK_CONSTRAINT_ATTRIBUTE_START:
case GTK_CONSTRAINT_ATTRIBUTE_END:
g_assert_not_reached ();
break ;
default :
break ;
}
return res;
}
static GtkConstraintVariable *
get_child_attribute (GtkConstraintLayout *layout,
GtkWidget *widget,
GtkConstraintAttribute attr)
{
GtkConstraintLayoutChild *child_info;
const char *prefix = gtk_widget_get_name (widget);
child_info = GTK_CONSTRAINT_LAYOUT_CHILD (gtk_layout_manager_get_layout_child (GTK_LAYOUT_MANAGER (layout), widget));
return gtk_constraint_layout_get_attribute (layout, attr, prefix, widget, child_info->bound_attributes);
}
static void
gtk_constraint_layout_child_finalize (GObject *gobject)
{
GtkConstraintLayoutChild *self = GTK_CONSTRAINT_LAYOUT_CHILD (gobject);
g_clear_pointer (&self->bound_attributes, g_hash_table_unref);
G_OBJECT_CLASS (gtk_constraint_layout_child_parent_class)->finalize (gobject);
}
static void
gtk_constraint_layout_child_class_init (GtkConstraintLayoutChildClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->finalize = gtk_constraint_layout_child_finalize;
}
static void
gtk_constraint_layout_child_init (GtkConstraintLayoutChild *self)
{
self->bound_attributes =
g_hash_table_new_full (g_str_hash, g_str_equal,
NULL,
(GDestroyNotify) gtk_constraint_variable_unref);
}
static void gtk_buildable_interface_init (GtkBuildableIface *iface);
G_DEFINE_TYPE_WITH_CODE (GtkConstraintLayout, gtk_constraint_layout, GTK_TYPE_LAYOUT_MANAGER,
G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, gtk_buildable_interface_init))
static void
gtk_constraint_layout_finalize (GObject *gobject)
{
GtkConstraintLayout *self = GTK_CONSTRAINT_LAYOUT (gobject);
if (self->constraints_observer)
{
g_list_store_remove_all (self->constraints_observer);
g_object_remove_weak_pointer ((GObject *)self->constraints_observer,
(gpointer *)&self->constraints_observer);
}
if (self->guides_observer)
{
g_list_store_remove_all (self->guides_observer);
g_object_remove_weak_pointer ((GObject *)self->guides_observer,
(gpointer *)&self->guides_observer);
}
g_clear_pointer (&self->bound_attributes, g_hash_table_unref);
g_clear_pointer (&self->constraints, g_hash_table_unref);
g_clear_pointer (&self->guides, g_hash_table_unref);
G_OBJECT_CLASS (gtk_constraint_layout_parent_class)->finalize (gobject);
}
static GtkConstraintVariable *
get_layout_attribute (GtkConstraintLayout *self,
GtkWidget *widget,
GtkConstraintAttribute attr)
{
GtkTextDirection text_dir;
const char *attr_name;
GtkConstraintVariable *res;
/* Resolve the start/end attributes depending on the layout's text direction */
if (attr == GTK_CONSTRAINT_ATTRIBUTE_START)
{
text_dir = gtk_widget_get_direction (widget);
if (text_dir == GTK_TEXT_DIR_RTL)
attr = GTK_CONSTRAINT_ATTRIBUTE_RIGHT;
else
attr = GTK_CONSTRAINT_ATTRIBUTE_LEFT;
}
else if (attr == GTK_CONSTRAINT_ATTRIBUTE_END)
{
text_dir = gtk_widget_get_direction (widget);
if (text_dir == GTK_TEXT_DIR_RTL)
attr = GTK_CONSTRAINT_ATTRIBUTE_LEFT;
else
attr = GTK_CONSTRAINT_ATTRIBUTE_RIGHT;
}
attr_name = get_attribute_name (attr);
res = g_hash_table_lookup (self->bound_attributes, attr_name);
if (res != NULL)
return res;
res = gtk_constraint_solver_create_variable (self->solver, "super" , attr_name, 0 .0 );
g_hash_table_insert (self->bound_attributes, (gpointer) attr_name, res);
/* Some attributes are really constraints computed from other
* attributes , to avoid creating additional constraints from
* the user ' s perspective
*/
switch (attr)
{
/* right = left + width */
case GTK_CONSTRAINT_ATTRIBUTE_RIGHT:
{
GtkConstraintExpressionBuilder builder;
GtkConstraintVariable *left, *width;
GtkConstraintExpression *expr;
left = get_layout_attribute (self, widget, GTK_CONSTRAINT_ATTRIBUTE_LEFT);
width = get_layout_attribute (self, widget, GTK_CONSTRAINT_ATTRIBUTE_WIDTH);
gtk_constraint_expression_builder_init (&builder, self->solver);
gtk_constraint_expression_builder_term (&builder, left);
gtk_constraint_expression_builder_plus (&builder);
gtk_constraint_expression_builder_term (&builder, width);
expr = gtk_constraint_expression_builder_finish (&builder);
gtk_constraint_solver_add_constraint (self->solver,
res, GTK_CONSTRAINT_RELATION_EQ, expr,
GTK_CONSTRAINT_STRENGTH_REQUIRED);
}
break ;
/* bottom = top + height */
case GTK_CONSTRAINT_ATTRIBUTE_BOTTOM:
{
GtkConstraintExpressionBuilder builder;
GtkConstraintVariable *top, *height;
GtkConstraintExpression *expr;
top = get_layout_attribute (self, widget, GTK_CONSTRAINT_ATTRIBUTE_TOP);
height = get_layout_attribute (self, widget, GTK_CONSTRAINT_ATTRIBUTE_HEIGHT);
gtk_constraint_expression_builder_init (&builder, self->solver);
gtk_constraint_expression_builder_term (&builder, top);
gtk_constraint_expression_builder_plus (&builder);
gtk_constraint_expression_builder_term (&builder, height);
expr = gtk_constraint_expression_builder_finish (&builder);
gtk_constraint_solver_add_constraint (self->solver,
res, GTK_CONSTRAINT_RELATION_EQ, expr,
GTK_CONSTRAINT_STRENGTH_REQUIRED);
}
break ;
/* centerX = left + (width / 2.0) */
case GTK_CONSTRAINT_ATTRIBUTE_CENTER_X:
{
GtkConstraintExpressionBuilder builder;
GtkConstraintVariable *left, *width;
GtkConstraintExpression *expr;
left = get_layout_attribute (self, widget, GTK_CONSTRAINT_ATTRIBUTE_LEFT);
width = get_layout_attribute (self, widget, GTK_CONSTRAINT_ATTRIBUTE_WIDTH);
gtk_constraint_expression_builder_init (&builder, self->solver);
gtk_constraint_expression_builder_term (&builder, width);
gtk_constraint_expression_builder_divide_by (&builder);
gtk_constraint_expression_builder_constant (&builder, 2 .0 );
gtk_constraint_expression_builder_plus (&builder);
gtk_constraint_expression_builder_term (&builder, left);
expr = gtk_constraint_expression_builder_finish (&builder);
gtk_constraint_solver_add_constraint (self->solver,
res, GTK_CONSTRAINT_RELATION_EQ, expr,
GTK_CONSTRAINT_STRENGTH_REQUIRED);
}
break ;
/* centerY = top + (height / 2.0) */
case GTK_CONSTRAINT_ATTRIBUTE_CENTER_Y:
{
GtkConstraintExpressionBuilder builder;
GtkConstraintVariable *top, *height;
GtkConstraintExpression *expr;
top = get_layout_attribute (self, widget, GTK_CONSTRAINT_ATTRIBUTE_TOP);
height = get_layout_attribute (self, widget, GTK_CONSTRAINT_ATTRIBUTE_HEIGHT);
gtk_constraint_expression_builder_init (&builder, self->solver);
gtk_constraint_expression_builder_term (&builder, height);
gtk_constraint_expression_builder_divide_by (&builder);
gtk_constraint_expression_builder_constant (&builder, 2 .0 );
gtk_constraint_expression_builder_plus (&builder);
gtk_constraint_expression_builder_term (&builder, top);
expr = gtk_constraint_expression_builder_finish (&builder);
gtk_constraint_solver_add_constraint (self->solver,
res, GTK_CONSTRAINT_RELATION_EQ, expr,
GTK_CONSTRAINT_STRENGTH_REQUIRED);
}
break ;
/* We do not allow negative sizes */
case GTK_CONSTRAINT_ATTRIBUTE_WIDTH:
case GTK_CONSTRAINT_ATTRIBUTE_HEIGHT:
{
GtkConstraintExpression *expr;
expr = gtk_constraint_expression_new (0 .0 );
gtk_constraint_solver_add_constraint (self->solver,
res, GTK_CONSTRAINT_RELATION_GE, expr,
GTK_CONSTRAINT_STRENGTH_REQUIRED);
}
break ;
/* These are "pure" attributes */
case GTK_CONSTRAINT_ATTRIBUTE_NONE:
case GTK_CONSTRAINT_ATTRIBUTE_LEFT:
case GTK_CONSTRAINT_ATTRIBUTE_TOP:
case GTK_CONSTRAINT_ATTRIBUTE_BASELINE:
break ;
/* These attributes must have been resolved to their real names */
case GTK_CONSTRAINT_ATTRIBUTE_START:
case GTK_CONSTRAINT_ATTRIBUTE_END:
g_assert_not_reached ();
break ;
default :
break ;
}
return res;
}
/*< private >
* layout_add_constraint :
* @ self : a ` GtkConstraintLayout `
* @ constraint : a [ class @ Gtk . Constraint ]
*
* Turns a ` GtkConstraint ` into a ` GtkConstraintRef ` inside the
* constraint solver associated to @ self .
*
* If @ self does not have a ` GtkConstraintSolver ` , because it
* has not been rooted yet , we just store the @ constraint instance ,
* and we ' re going to call this function when the layout manager
* gets rooted .
*/
static void
layout_add_constraint (GtkConstraintLayout *self,
GtkConstraint *constraint)
{
GtkConstraintVariable *target_attr, *source_attr;
GtkConstraintExpressionBuilder builder;
GtkConstraintExpression *expr;
GtkConstraintSolver *solver;
GtkConstraintAttribute attr;
GtkConstraintTarget *target, *source;
GtkWidget *layout_widget;
if (gtk_constraint_is_attached (constraint))
return ;
/* Once we pass the preconditions, we check if we can turn a GtkConstraint
* into a GtkConstraintRef ; if we can ' t , we keep a reference to the
* constraint object and try later on
*/
layout_widget = gtk_layout_manager_get_widget (GTK_LAYOUT_MANAGER (self));
if (layout_widget == NULL)
return ;
solver = gtk_constraint_layout_get_solver (self);
if (solver == NULL)
return ;
attr = gtk_constraint_get_target_attribute (constraint);
target = gtk_constraint_get_target (constraint);
if (target == NULL || target == GTK_CONSTRAINT_TARGET (layout_widget))
{
/* A NULL target widget is assumed to be referring to the layout itself */
target_attr = get_layout_attribute (self, layout_widget, attr);
}
else if (GTK_IS_WIDGET (target) &&
gtk_widget_get_parent (GTK_WIDGET (target)) == layout_widget)
{
target_attr = get_child_attribute (self, GTK_WIDGET (target), attr);
}
else if (GTK_IS_CONSTRAINT_GUIDE (target))
{
GtkConstraintGuide *guide;
guide = (GtkConstraintGuide*)g_hash_table_lookup (self->guides, target);
target_attr = gtk_constraint_guide_get_attribute (guide, attr);
}
else
{
g_critical ("Unknown target widget '%p'" , target);
target_attr = NULL;
}
if (target_attr == NULL)
return ;
attr = gtk_constraint_get_source_attribute (constraint);
source = gtk_constraint_get_source (constraint);
/* The constraint is a constant */
if (attr == GTK_CONSTRAINT_ATTRIBUTE_NONE)
{
source_attr = NULL;
}
else
{
if (source == NULL || source == GTK_CONSTRAINT_TARGET (layout_widget))
{
source_attr = get_layout_attribute (self, layout_widget, attr);
}
else if (GTK_IS_WIDGET (source) &&
gtk_widget_get_parent (GTK_WIDGET (source)) == layout_widget)
{
source_attr = get_child_attribute (self, GTK_WIDGET (source), attr);
}
else if (GTK_IS_CONSTRAINT_GUIDE (source))
{
GtkConstraintGuide *guide;
guide = (GtkConstraintGuide*)g_hash_table_lookup (self->guides, source);
source_attr = gtk_constraint_guide_get_attribute (guide, attr);
}
else
{
g_critical ("Unknown source widget '%p'" , source);
source_attr = NULL;
return ;
}
}
/* Build the expression */
gtk_constraint_expression_builder_init (&builder, self->solver);
if (source_attr != NULL)
{
gtk_constraint_expression_builder_term (&builder, source_attr);
gtk_constraint_expression_builder_multiply_by (&builder);
gtk_constraint_expression_builder_constant (&builder, gtk_constraint_get_multiplier (constraint));
gtk_constraint_expression_builder_plus (&builder);
}
gtk_constraint_expression_builder_constant (&builder, gtk_constraint_get_constant (constraint));
expr = gtk_constraint_expression_builder_finish (&builder);
constraint->solver = solver;
constraint->constraint_ref =
gtk_constraint_solver_add_constraint (self->solver,
target_attr,
gtk_constraint_get_relation (constraint),
expr,
gtk_constraint_get_strength (constraint));
}
static void
update_child_constraint (GtkConstraintLayout *self,
GtkConstraintLayoutChild *child_info,
GtkWidget *child,
int index,
int value)
{
GtkConstraintVariable *var;
int attr[LAST_VALUE] = {
GTK_CONSTRAINT_ATTRIBUTE_WIDTH,
GTK_CONSTRAINT_ATTRIBUTE_HEIGHT,
GTK_CONSTRAINT_ATTRIBUTE_WIDTH,
GTK_CONSTRAINT_ATTRIBUTE_HEIGHT
};
int relation[LAST_VALUE] = {
GTK_CONSTRAINT_RELATION_GE,
GTK_CONSTRAINT_RELATION_GE,
GTK_CONSTRAINT_RELATION_EQ,
GTK_CONSTRAINT_RELATION_EQ
};
if (child_info->values[index] != value)
{
child_info->values[index] = value;
if (child_info->constraints[index])
gtk_constraint_solver_remove_constraint (self->solver,
child_info->constraints[index]);
var = get_child_attribute (self, child, attr[index]);
if (relation[index] == GTK_CONSTRAINT_RELATION_EQ)
{
gtk_constraint_variable_set_value (var, value);
child_info->constraints[index] =
gtk_constraint_solver_add_stay_variable (self->solver,
var,
GTK_CONSTRAINT_STRENGTH_MEDIUM);
}
else
{
child_info->constraints[index] =
gtk_constraint_solver_add_constraint (self->solver,
var,
relation[index],
gtk_constraint_expression_new (value),
GTK_CONSTRAINT_STRENGTH_REQUIRED);
}
}
}
static void
gtk_constraint_layout_measure (GtkLayoutManager *manager,
GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline)
{
GtkConstraintLayout *self = GTK_CONSTRAINT_LAYOUT (manager);
GtkConstraintVariable *size, *opposite_size;
GtkConstraintSolver *solver;
GtkWidget *child;
int min_value;
int nat_value;
solver = gtk_constraint_layout_get_solver (self);
if (solver == NULL)
return ;
gtk_constraint_solver_freeze (solver);
/* We measure each child in the layout and impose restrictions on the
* minimum and natural size , so we can solve the size of the overall
* layout later on
*/
for (child = _gtk_widget_get_first_child (widget);
child != NULL;
child = _gtk_widget_get_next_sibling (child))
{
GtkConstraintLayoutChild *info;
GtkRequisition min_req, nat_req;
if (!gtk_widget_should_layout (child))
continue ;
gtk_widget_get_preferred_size (child, &min_req, &nat_req);
info = GTK_CONSTRAINT_LAYOUT_CHILD (gtk_layout_manager_get_layout_child (manager, child));
update_child_constraint (self, info, child, MIN_WIDTH, min_req.width);
update_child_constraint (self, info, child, MIN_HEIGHT, min_req.height);
update_child_constraint (self, info, child, NAT_WIDTH, nat_req.width);
update_child_constraint (self, info, child, NAT_HEIGHT, nat_req.height);
}
gtk_constraint_solver_thaw (solver);
switch (orientation)
{
case GTK_ORIENTATION_HORIZONTAL:
size = get_layout_attribute (self, widget, GTK_CONSTRAINT_ATTRIBUTE_WIDTH);
opposite_size = get_layout_attribute (self, widget, GTK_CONSTRAINT_ATTRIBUTE_HEIGHT);
break ;
case GTK_ORIENTATION_VERTICAL:
size = get_layout_attribute (self, widget, GTK_CONSTRAINT_ATTRIBUTE_HEIGHT);
opposite_size = get_layout_attribute (self, widget, GTK_CONSTRAINT_ATTRIBUTE_WIDTH);
break ;
default :
g_assert_not_reached ();
}
g_assert (size != NULL && opposite_size != NULL);
nat_value = gtk_constraint_variable_get_value (size);
/* We impose a temporary value on the size and opposite size of the
* layout , with a low weight to let the solver settle towards the
* natural state of the system . Once we get the value out , we can
* remove these constraints
*/
gtk_constraint_solver_add_edit_variable (solver, size, GTK_CONSTRAINT_STRENGTH_STRONG * 2 );
if (for_size > 0 )
gtk_constraint_solver_add_edit_variable (solver, opposite_size, GTK_CONSTRAINT_STRENGTH_STRONG * 2 );
gtk_constraint_solver_begin_edit (solver);
gtk_constraint_solver_suggest_value (solver, size, 0 .0 );
if (for_size > 0 )
gtk_constraint_solver_suggest_value (solver, opposite_size, for_size);
gtk_constraint_solver_resolve (solver);
min_value = gtk_constraint_variable_get_value (size);
gtk_constraint_solver_remove_edit_variable (solver, size);
if (for_size > 0 )
gtk_constraint_solver_remove_edit_variable (solver, opposite_size);
gtk_constraint_solver_end_edit (solver);
GTK_DEBUG (LAYOUT, "layout %p %s size: min %d nat %d (for opposite size: %d)" ,
self,
orientation == GTK_ORIENTATION_HORIZONTAL ? "horizontal" : "vertical" ,
min_value, nat_value,
for_size);
if (minimum != NULL)
*minimum = min_value;
if (natural != NULL)
*natural = nat_value;
}
static void
gtk_constraint_layout_allocate (GtkLayoutManager *manager,
GtkWidget *widget,
int width,
int height,
int baseline)
{
GtkConstraintLayout *self = GTK_CONSTRAINT_LAYOUT (manager);
GtkConstraintRef *stay_w, *stay_h, *stay_t, *stay_l;
GtkConstraintSolver *solver;
GtkConstraintVariable *layout_top, *layout_height;
GtkConstraintVariable *layout_left, *layout_width;
GtkWidget *child;
solver = gtk_constraint_layout_get_solver (self);
if (solver == NULL)
return ;
/* We add required stay constraints to ensure that the layout remains
* within the bounds of the allocation
*/
layout_top = get_layout_attribute (self, widget, GTK_CONSTRAINT_ATTRIBUTE_TOP);
layout_left = get_layout_attribute (self, widget, GTK_CONSTRAINT_ATTRIBUTE_LEFT);
layout_width = get_layout_attribute (self, widget, GTK_CONSTRAINT_ATTRIBUTE_WIDTH);
layout_height = get_layout_attribute (self, widget, GTK_CONSTRAINT_ATTRIBUTE_HEIGHT);
gtk_constraint_variable_set_value (layout_top, 0 .0 );
stay_t = gtk_constraint_solver_add_stay_variable (solver,
layout_top,
GTK_CONSTRAINT_STRENGTH_REQUIRED);
gtk_constraint_variable_set_value (layout_left, 0 .0 );
stay_l = gtk_constraint_solver_add_stay_variable (solver,
layout_left,
GTK_CONSTRAINT_STRENGTH_REQUIRED);
gtk_constraint_variable_set_value (layout_width, width);
stay_w = gtk_constraint_solver_add_stay_variable (solver,
layout_width,
GTK_CONSTRAINT_STRENGTH_REQUIRED);
gtk_constraint_variable_set_value (layout_height, height);
stay_h = gtk_constraint_solver_add_stay_variable (solver,
layout_height,
GTK_CONSTRAINT_STRENGTH_REQUIRED);
GTK_DEBUG (LAYOUT, "Layout [%p]: { .x: %g, .y: %g, .w: %g, .h: %g }" ,
self,
gtk_constraint_variable_get_value (layout_left),
gtk_constraint_variable_get_value (layout_top),
gtk_constraint_variable_get_value (layout_width),
gtk_constraint_variable_get_value (layout_height));
for (child = _gtk_widget_get_first_child (widget);
child != NULL;
child = _gtk_widget_get_next_sibling (child))
{
GtkConstraintVariable *var_top, *var_left, *var_width, *var_height;
GtkConstraintVariable *var_baseline;
GtkAllocation child_alloc;
int child_baseline = -1 ;
if (!gtk_widget_should_layout (child))
continue ;
/* Retrieve all the values associated with the child */
var_top = get_child_attribute (self, child, GTK_CONSTRAINT_ATTRIBUTE_TOP);
var_left = get_child_attribute (self, child, GTK_CONSTRAINT_ATTRIBUTE_LEFT);
var_width = get_child_attribute (self, child, GTK_CONSTRAINT_ATTRIBUTE_WIDTH);
var_height = get_child_attribute (self, child, GTK_CONSTRAINT_ATTRIBUTE_HEIGHT);
var_baseline = get_child_attribute (self, child, GTK_CONSTRAINT_ATTRIBUTE_BASELINE);
GTK_DEBUG (LAYOUT, "Allocating child '%s'[%p] with { .x: %g, .y: %g, .w: %g, .h: %g, .b: %g }" ,
gtk_widget_get_name (child), child,
gtk_constraint_variable_get_value (var_left),
gtk_constraint_variable_get_value (var_top),
gtk_constraint_variable_get_value (var_width),
gtk_constraint_variable_get_value (var_height),
gtk_constraint_variable_get_value (var_baseline));
child_alloc.x = floor (gtk_constraint_variable_get_value (var_left));
child_alloc.y = floor (gtk_constraint_variable_get_value (var_top));
child_alloc.width = ceil (gtk_constraint_variable_get_value (var_width));
child_alloc.height = ceil (gtk_constraint_variable_get_value (var_height));
if (gtk_constraint_variable_get_value (var_baseline) > 0 )
child_baseline = floor (gtk_constraint_variable_get_value (var_baseline));
gtk_widget_size_allocate (GTK_WIDGET (child),
&child_alloc,
child_baseline);
}
if (GTK_DEBUG_CHECK (LAYOUT))
{
GHashTableIter iter;
gpointer key;
g_hash_table_iter_init (&iter, self->guides);
while (g_hash_table_iter_next (&iter, &key, NULL))
{
GtkConstraintGuide *guide = key;
GtkConstraintVariable *var_top, *var_left, *var_width, *var_height;
var_top = gtk_constraint_guide_get_attribute (guide, GTK_CONSTRAINT_ATTRIBUTE_TOP);
var_left = gtk_constraint_guide_get_attribute (guide, GTK_CONSTRAINT_ATTRIBUTE_LEFT);
var_width = gtk_constraint_guide_get_attribute (guide, GTK_CONSTRAINT_ATTRIBUTE_WIDTH);
var_height = gtk_constraint_guide_get_attribute (guide, GTK_CONSTRAINT_ATTRIBUTE_HEIGHT);
g_print ("Allocating guide '%s'[%p] with { .x: %g .y: %g .w: %g .h: %g }\n" ,
gtk_constraint_guide_get_name (guide), guide,
gtk_constraint_variable_get_value (var_left),
gtk_constraint_variable_get_value (var_top),
gtk_constraint_variable_get_value (var_width),
gtk_constraint_variable_get_value (var_height));
}
}
/* The allocation stay constraints are not needed any more */
gtk_constraint_solver_remove_constraint (solver, stay_w);
gtk_constraint_solver_remove_constraint (solver, stay_h);
gtk_constraint_solver_remove_constraint (solver, stay_t);
gtk_constraint_solver_remove_constraint (solver, stay_l);
}
static void
gtk_constraint_layout_root (GtkLayoutManager *manager)
{
GtkConstraintLayout *self = GTK_CONSTRAINT_LAYOUT (manager);
GHashTableIter iter;
GtkWidget *widget;
GtkRoot *root;
gpointer key;
widget = gtk_layout_manager_get_widget (manager);
root = gtk_widget_get_root (widget);
self->solver = gtk_root_get_constraint_solver (root);
/* Now that we have a solver, attach all constraints we have */
g_hash_table_iter_init (&iter, self->constraints);
while (g_hash_table_iter_next (&iter, &key, NULL))
{
GtkConstraint *constraint = key;
layout_add_constraint (self, constraint);
}
g_hash_table_iter_init (&iter, self->guides);
while (g_hash_table_iter_next (&iter, &key, NULL))
{
GtkConstraintGuide *guide = key;
gtk_constraint_guide_update (guide);
}
}
static void
gtk_constraint_layout_unroot (GtkLayoutManager *manager)
{
GtkConstraintLayout *self = GTK_CONSTRAINT_LAYOUT (manager);
GHashTableIter iter;
gpointer key;
/* Detach all constraints we're holding, as we're removing the layout
* from the global solver , and they should not contribute to the other
* layouts
*/
g_hash_table_iter_init (&iter, self->constraints);
while (g_hash_table_iter_next (&iter, &key, NULL))
{
GtkConstraint *constraint = key;
gtk_constraint_detach (constraint);
}
g_hash_table_iter_init (&iter, self->guides);
while (g_hash_table_iter_next (&iter, &key, NULL))
{
GtkConstraintGuide *guide = key;
gtk_constraint_guide_detach (guide);
}
self->solver = NULL;
}
static void
gtk_constraint_layout_class_init (GtkConstraintLayoutClass *klass)
{
GtkLayoutManagerClass *manager_class = GTK_LAYOUT_MANAGER_CLASS (klass);
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->finalize = gtk_constraint_layout_finalize;
manager_class->layout_child_type = GTK_TYPE_CONSTRAINT_LAYOUT_CHILD;
manager_class->measure = gtk_constraint_layout_measure;
manager_class->allocate = gtk_constraint_layout_allocate;
manager_class->root = gtk_constraint_layout_root;
manager_class->unroot = gtk_constraint_layout_unroot;
}
static void
gtk_constraint_layout_init (GtkConstraintLayout *self)
{
/* The bound variables in the solver */
self->bound_attributes =
g_hash_table_new_full (g_str_hash, g_str_equal,
NULL,
(GDestroyNotify) gtk_constraint_variable_unref);
/* The GtkConstraint instances we own */
self->constraints =
g_hash_table_new_full (NULL, NULL,
(GDestroyNotify) g_object_unref,
NULL);
self->guides =
g_hash_table_new_full (NULL, NULL,
(GDestroyNotify) g_object_unref,
NULL);
}
typedef struct {
GtkConstraintLayout *layout;
GtkBuilder *builder;
GList *constraints;
GList *guides;
} ConstraintsParserData;
typedef struct {
char *source_name;
char *source_attr;
char *target_name;
char *target_attr;
char *relation;
char *strength;
double constant;
double multiplier;
} ConstraintData;
typedef struct {
char *name;
char *strength;
struct {
int min, nat, max;
} sizes[2 ];
} GuideData;
static void
constraint_data_free (gpointer _data)
{
ConstraintData *data = _data;
g_free (data->source_name);
g_free (data->source_attr);
g_free (data->target_name);
g_free (data->target_attr);
g_free (data->relation);
g_free (data->strength);
g_free (data);
}
static void
guide_data_free (gpointer _data)
{
GuideData *data = _data;
g_free (data->name);
g_free (data->strength);
g_free (data);
}
static void
parse_double (const char *string,
double *value_p,
double default_value)
{
double value;
char *endptr;
int saved_errno;
if (string == NULL || string[0 ] == '\0' )
{
*value_p = default_value;
return ;
}
saved_errno = errno;
errno = 0 ;
value = g_ascii_strtod (string, &endptr);
if (errno == 0 && endptr != string)
*value_p = value;
else
*value_p = default_value;
errno = saved_errno;
}
static void
parse_int (const char *string,
int *value_p,
int default_value)
{
gint64 value;
char *endptr;
int saved_errno;
if (string == NULL || string[0 ] == '\0' )
{
*value_p = default_value;
return ;
}
saved_errno = errno;
errno = 0 ;
value = g_ascii_strtoll (string, &endptr, 10 );
if (errno == 0 && endptr != string)
*value_p = (int ) value;
else
*value_p = default_value;
errno = saved_errno;
}
static GtkConstraint *
constraint_data_to_constraint (const ConstraintData *data,
GtkBuilder *builder,
GHashTable *guides,
GError **error)
{
gpointer source, target;
int source_attr, target_attr;
int relation, strength;
gboolean res;
if (g_strcmp0 (data->source_name, "super" ) == 0 )
source = NULL;
else if (data->source_name == NULL)
{
if (data->source_attr != NULL)
{
g_set_error (error, GTK_BUILDER_ERROR,
GTK_BUILDER_ERROR_INVALID_VALUE,
"Constraints without 'source' must also not "
"have a 'source-attribute' attribute" );
return NULL;
}
source = NULL;
}
else
{
if (g_hash_table_contains (guides, data->source_name))
source = g_hash_table_lookup (guides, data->source_name);
else
source = gtk_builder_get_object (builder, data->source_name);
if (source == NULL)
{
g_set_error (error, GTK_BUILDER_ERROR,
GTK_BUILDER_ERROR_INVALID_VALUE,
"Unable to find source '%s' for constraint" ,
data->source_name);
return NULL;
}
}
if (g_strcmp0 (data->target_name, "super" ) == 0 )
target = NULL;
else
{
if (g_hash_table_contains (guides, data->target_name))
target = g_hash_table_lookup (guides, data->target_name);
else
target = gtk_builder_get_object (builder, data->target_name);
if (target == NULL)
{
g_set_error (error, GTK_BUILDER_ERROR,
GTK_BUILDER_ERROR_INVALID_VALUE,
"Unable to find target '%s' for constraint" ,
data->target_name);
return NULL;
}
}
if (data->source_attr != NULL)
{
res = _gtk_builder_enum_from_string (GTK_TYPE_CONSTRAINT_ATTRIBUTE,
data->source_attr,
&source_attr,
error);
if (!res)
return NULL;
}
else
source_attr = GTK_CONSTRAINT_ATTRIBUTE_NONE;
res = _gtk_builder_enum_from_string (GTK_TYPE_CONSTRAINT_ATTRIBUTE,
data->target_attr,
&target_attr,
error);
if (!res)
return NULL;
if (data->relation != NULL)
{
res = _gtk_builder_enum_from_string (GTK_TYPE_CONSTRAINT_RELATION,
data->relation,
&relation,
error);
if (!res)
return NULL;
}
else
relation = GTK_CONSTRAINT_RELATION_EQ;
if (data->strength != NULL)
{
res = _gtk_builder_enum_from_string (GTK_TYPE_CONSTRAINT_STRENGTH,
data->strength,
&strength,
error);
if (!res)
return NULL;
}
else
strength = GTK_CONSTRAINT_STRENGTH_REQUIRED;
if (source == NULL && source_attr == GTK_CONSTRAINT_ATTRIBUTE_NONE)
return gtk_constraint_new_constant (target, target_attr,
relation,
data->constant,
strength);
else
return gtk_constraint_new (target, target_attr,
relation,
source, source_attr,
data->multiplier,
data->constant,
strength);
}
static GtkConstraintGuide *
guide_data_to_guide (const GuideData *data,
GtkBuilder *builder,
GError **error)
{
int strength;
gboolean res;
if (data->strength != NULL)
{
res = _gtk_builder_enum_from_string (GTK_TYPE_CONSTRAINT_STRENGTH,
data->strength,
&strength,
error);
if (!res)
return NULL;
}
else
strength = GTK_CONSTRAINT_STRENGTH_MEDIUM;
return g_object_new (GTK_TYPE_CONSTRAINT_GUIDE,
"min-width" , data->sizes[GTK_ORIENTATION_HORIZONTAL].min,
"nat-width" , data->sizes[GTK_ORIENTATION_HORIZONTAL].nat,
"max-width" , data->sizes[GTK_ORIENTATION_HORIZONTAL].max,
"min-height" , data->sizes[GTK_ORIENTATION_VERTICAL].min,
"nat-height" , data->sizes[GTK_ORIENTATION_VERTICAL].nat,
"max-height" , data->sizes[GTK_ORIENTATION_VERTICAL].max,
"strength" , strength,
"name" , data->name,
NULL);
}
static void
constraints_start_element (GtkBuildableParseContext *context,
const char *element_name,
const char **attr_names,
const char **attr_values,
gpointer user_data,
GError **error)
{
ConstraintsParserData *data = user_data;
if (strcmp (element_name, "constraints" ) == 0 )
{
if (!_gtk_builder_check_parent (data->builder, context, "object" , error))
return ;
if (!g_markup_collect_attributes (element_name, attr_names, attr_values, error,
G_MARKUP_COLLECT_INVALID, NULL, NULL,
G_MARKUP_COLLECT_INVALID))
_gtk_builder_prefix_error (data->builder, context, error);
}
else if (strcmp (element_name, "constraint" ) == 0 )
{
const char *target_name, *target_attribute;
const char *relation_str = NULL;
const char *source_name = NULL, *source_attribute = NULL;
const char *multiplier_str = NULL, *constant_str = NULL;
const char *strength_str = NULL;
ConstraintData *cdata;
if (!_gtk_builder_check_parent (data->builder, context, "constraints" , error))
return ;
if (!g_markup_collect_attributes (element_name, attr_names, attr_values, error,
G_MARKUP_COLLECT_STRING, "target" , &target_name,
G_MARKUP_COLLECT_STRING, "target-attribute" , &target_attribute,
G_MARKUP_COLLECT_STRING, "relation" , &relation_str,
G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "source" , &source_name,
G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "source-attribute" , &source_attribute,
G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "multiplier" , &multiplier_str,
G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "constant" , &constant_str,
G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "strength" , &strength_str,
G_MARKUP_COLLECT_INVALID))
{
_gtk_builder_prefix_error (data->builder, context, error);
return ;
}
cdata = g_new0 (ConstraintData, 1 );
cdata->target_name = g_strdup (target_name);
cdata->target_attr = g_strdup (target_attribute);
cdata->relation = g_strdup (relation_str);
cdata->source_name = g_strdup (source_name);
cdata->source_attr = g_strdup (source_attribute);
parse_double (multiplier_str, &cdata->multiplier, 1 .0 );
parse_double (constant_str, &cdata->constant, 0 .0 );
cdata->strength = g_strdup (strength_str);
data->constraints = g_list_prepend (data->constraints, cdata);
}
else if (strcmp (element_name, "guide" ) == 0 )
{
const char *min_width, *nat_width, *max_width;
const char *min_height, *nat_height, *max_height;
const char *strength_str;
const char *name;
GuideData *gdata;
if (!_gtk_builder_check_parent (data->builder, context, "constraints" , error))
return ;
if (!g_markup_collect_attributes (element_name, attr_names, attr_values, error,
G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "min-width" , &min_width,
G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "nat-width" , &nat_width,
G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "max-width" , &max_width,
G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "min-height" , &min_height,
G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "nat-height" , &nat_height,
G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "max-height" , &max_height,
G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "strength" , &strength_str,
G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "name" , &name,
G_MARKUP_COLLECT_INVALID))
{
_gtk_builder_prefix_error (data->builder, context, error);
return ;
}
gdata = g_new0 (GuideData, 1 );
parse_int (min_width, &(gdata->sizes[GTK_ORIENTATION_HORIZONTAL].min), 0 );
parse_int (nat_width, &(gdata->sizes[GTK_ORIENTATION_HORIZONTAL].nat), 0 );
parse_int (max_width, &(gdata->sizes[GTK_ORIENTATION_HORIZONTAL].max), G_MAXINT);
parse_int (min_height, &(gdata->sizes[GTK_ORIENTATION_VERTICAL].min), 0 );
parse_int (nat_height, &(gdata->sizes[GTK_ORIENTATION_VERTICAL].nat), 0 );
parse_int (max_height, &(gdata->sizes[GTK_ORIENTATION_VERTICAL].max), G_MAXINT);
gdata->name = g_strdup (name);
gdata->strength = g_strdup (strength_str);
data->guides = g_list_prepend (data->guides, gdata);
}
else
{
_gtk_builder_error_unhandled_tag (data->builder, context,
"GtkConstraintLayout" , element_name,
error);
}
}
static void
constraints_end_element (GtkBuildableParseContext *context,
const char *element_name,
gpointer user_data,
GError **error)
{
}
static const GtkBuildableParser constraints_parser = {
constraints_start_element,
constraints_end_element,
NULL,
};
static gboolean
gtk_constraint_layout_custom_tag_start (GtkBuildable *buildable,
GtkBuilder *builder,
GObject *child,
const char *element_name,
GtkBuildableParser *parser,
gpointer *parser_data)
{
if (strcmp (element_name, "constraints" ) == 0 )
{
ConstraintsParserData *data = g_new (ConstraintsParserData, 1 );
data->layout = g_object_ref (GTK_CONSTRAINT_LAYOUT (buildable));
data->builder = builder;
data->constraints = NULL;
data->guides = NULL;
*parser = constraints_parser;
*parser_data = data;
return TRUE ;
}
return FALSE ;
}
static void
gtk_constraint_layout_custom_tag_end (GtkBuildable *buildable,
GtkBuilder *builder,
GObject *child,
const char *element_name,
gpointer data)
{
}
static void
gtk_constraint_layout_custom_finished (GtkBuildable *buildable,
GtkBuilder *builder,
GObject *child,
const char *element_name,
gpointer user_data)
{
ConstraintsParserData *data = user_data;
if (strcmp (element_name, "constraints" ) == 0 )
{
GList *l;
GHashTable *guides;
guides = g_hash_table_new (g_str_hash, g_str_equal);
data->guides = g_list_reverse (data->guides);
for (l = data->guides; l != NULL; l = l->next)
{
const GuideData *gdata = l->data;
GtkConstraintGuide *g;
GError *error = NULL;
const char *name;
g = guide_data_to_guide (gdata, builder, &error);
if (error != NULL)
{
g_critical ("Unable to parse guide definition: %s" , error->message);
g_error_free (error);
continue ;
}
name = gtk_constraint_guide_get_name (g);
if (g_hash_table_lookup (guides, name))
{
g_critical ("Duplicate guide: %s" , name);
g_object_unref (g);
continue ;
}
g_hash_table_insert (guides, (gpointer)name, g);
gtk_constraint_layout_add_guide (data->layout, g);
}
data->constraints = g_list_reverse (data->constraints);
for (l = data->constraints; l != NULL; l = l->next)
{
const ConstraintData *cdata = l->data;
GtkConstraint *c;
GError *error = NULL;
c = constraint_data_to_constraint (cdata, builder, guides, &error);
if (error != NULL)
{
g_critical ("Unable to parse constraint definition '%s.%s [%s] %s.%s * %g + %g': %s" ,
cdata->target_name, cdata->target_attr,
cdata->relation,
cdata->source_name, cdata->source_attr,
cdata->multiplier,
cdata->constant,
error->message);
g_error_free (error);
continue ;
}
layout_add_constraint (data->layout, c);
g_hash_table_add (data->layout->constraints, c);
if (data->layout->constraints_observer)
g_list_store_append (data->layout->constraints_observer, c);
}
gtk_layout_manager_layout_changed (GTK_LAYOUT_MANAGER (data->layout));
g_list_free_full (data->constraints, constraint_data_free);
g_list_free_full (data->guides, guide_data_free);
g_object_unref (data->layout);
g_free (data);
g_hash_table_unref (guides);
}
}
static void
gtk_buildable_interface_init (GtkBuildableIface *iface)
{
iface->custom_tag_start = gtk_constraint_layout_custom_tag_start;
iface->custom_tag_end = gtk_constraint_layout_custom_tag_end;
iface->custom_finished = gtk_constraint_layout_custom_finished;
}
/**
* gtk_constraint_layout_new :
*
* Creates a new ` GtkConstraintLayout ` layout manager .
*
* Returns : the newly created ` GtkConstraintLayout `
*/
GtkLayoutManager *
gtk_constraint_layout_new (void )
{
return g_object_new (GTK_TYPE_CONSTRAINT_LAYOUT, NULL);
}
/**
* gtk_constraint_layout_add_constraint :
* @ layout : a ` GtkConstraintLayout `
* @ constraint : ( transfer full ) : a [ class @ Gtk . Constraint ]
*
* Adds a constraint to the layout manager .
*
* The [ property @ Gtk . Constraint : source ] and [ property @ Gtk . Constraint : target ]
* properties of ` constraint ` can be :
*
* - set to ` NULL ` to indicate that the constraint refers to the
* widget using ` layout `
* - set to the [ class @ Gtk . Widget ] using ` layout `
* - set to a child of the [ class @ Gtk . Widget ] using ` layout `
* - set to a [ class @ Gtk . ConstraintGuide ] that is part of ` layout `
*
* The @ layout acquires the ownership of @ constraint after calling
* this function .
*/
void
gtk_constraint_layout_add_constraint (GtkConstraintLayout *layout,
GtkConstraint *constraint)
{
g_return_if_fail (GTK_IS_CONSTRAINT_LAYOUT (layout));
g_return_if_fail (GTK_IS_CONSTRAINT (constraint));
g_return_if_fail (!gtk_constraint_is_attached (constraint));
layout_add_constraint (layout, constraint);
g_hash_table_add (layout->constraints, constraint);
if (layout->constraints_observer)
g_list_store_append (layout->constraints_observer, constraint);
gtk_layout_manager_layout_changed (GTK_LAYOUT_MANAGER (layout));
}
static void
list_store_remove_item (GListStore *store,
gpointer item)
{
int n_items;
int i;
n_items = g_list_model_get_n_items (G_LIST_MODEL (store));
for (i = 0 ; i < n_items; i++)
{
gpointer *model_item = g_list_model_get_item (G_LIST_MODEL (store), i);
g_object_unref (model_item);
if (item == model_item)
{
g_list_store_remove (store, i);
break ;
}
}
}
/**
* gtk_constraint_layout_remove_constraint :
* @ layout : a ` GtkConstraintLayout `
* @ constraint : a [ class @ Gtk . Constraint ]
*
* Removes ` constraint ` from the layout manager ,
* so that it no longer influences the layout .
*/
void
gtk_constraint_layout_remove_constraint (GtkConstraintLayout *layout,
GtkConstraint *constraint)
{
g_return_if_fail (GTK_IS_CONSTRAINT_LAYOUT (layout));
g_return_if_fail (GTK_IS_CONSTRAINT (constraint));
g_return_if_fail (gtk_constraint_is_attached (constraint));
gtk_constraint_detach (constraint);
g_hash_table_remove (layout->constraints, constraint);
if (layout->constraints_observer)
list_store_remove_item (layout->constraints_observer, constraint);
gtk_layout_manager_layout_changed (GTK_LAYOUT_MANAGER (layout));
}
/**
* gtk_constraint_layout_remove_all_constraints :
* @ layout : a ` GtkConstraintLayout `
*
* Removes all constraints from the layout manager .
*/
void
gtk_constraint_layout_remove_all_constraints (GtkConstraintLayout *layout)
{
GHashTableIter iter;
gpointer key;
g_return_if_fail (GTK_IS_CONSTRAINT_LAYOUT (layout));
g_hash_table_iter_init (&iter, layout->constraints);
while (g_hash_table_iter_next (&iter, &key, NULL))
{
GtkConstraint *constraint = key;
gtk_constraint_detach (constraint);
g_hash_table_iter_remove (&iter);
}
if (layout->constraints_observer)
g_list_store_remove_all (layout->constraints_observer);
gtk_layout_manager_layout_changed (GTK_LAYOUT_MANAGER (layout));
}
/**
* gtk_constraint_layout_add_guide :
* @ layout : a ` GtkConstraintLayout `
* @ guide : ( transfer full ) : a [ class @ Gtk . ConstraintGuide ] object
*
* Adds a guide to ` layout ` .
*
* A guide can be used as the source or target of constraints ,
* like a widget , but it is not visible .
*
* The ` layout ` acquires the ownership of ` guide ` after calling
* this function .
*/
void
gtk_constraint_layout_add_guide (GtkConstraintLayout *layout,
GtkConstraintGuide *guide)
{
g_return_if_fail (GTK_IS_CONSTRAINT_LAYOUT (layout));
g_return_if_fail (GTK_IS_CONSTRAINT_GUIDE (guide));
g_return_if_fail (gtk_constraint_guide_get_layout (guide) == NULL);
gtk_constraint_guide_set_layout (guide, layout);
g_hash_table_add (layout->guides, guide);
if (layout->guides_observer)
g_list_store_append (layout->guides_observer, guide);
gtk_constraint_guide_update (guide);
gtk_layout_manager_layout_changed (GTK_LAYOUT_MANAGER (layout));
}
/**
* gtk_constraint_layout_remove_guide :
* @ layout : a ` GtkConstraintLayout `
* @ guide : a [ class @ Gtk . ConstraintGuide ] object
*
* Removes ` guide ` from the layout manager ,
* so that it no longer influences the layout .
*/
void
gtk_constraint_layout_remove_guide (GtkConstraintLayout *layout,
GtkConstraintGuide *guide)
{
g_return_if_fail (GTK_IS_CONSTRAINT_LAYOUT (layout));
g_return_if_fail (GTK_IS_CONSTRAINT_GUIDE (guide));
g_return_if_fail (gtk_constraint_guide_get_layout (guide) == layout);
gtk_constraint_guide_detach (guide);
gtk_constraint_guide_set_layout (guide, NULL);
g_hash_table_remove (layout->guides, guide);
if (layout->guides_observer)
list_store_remove_item (layout->guides_observer, guide);
gtk_layout_manager_layout_changed (GTK_LAYOUT_MANAGER (layout));
}
static GtkConstraintAttribute
attribute_from_name (const char *name)
{
if (name == NULL || *name == '\0' )
return GTK_CONSTRAINT_ATTRIBUTE_NONE;
/* We sadly need to special case these two because the name does
* not match the VFL grammar rules
*/
if (strcmp (name, "centerX" ) == 0 )
return GTK_CONSTRAINT_ATTRIBUTE_CENTER_X;
if (strcmp (name, "centerY" ) == 0 )
return GTK_CONSTRAINT_ATTRIBUTE_CENTER_Y;
for (int i = 0 ; i < G_N_ELEMENTS (attribute_names); i++)
{
if (strcmp (attribute_names[i], name) == 0 )
return i;
}
return GTK_CONSTRAINT_ATTRIBUTE_NONE;
}
/**
* gtk_constraint_vfl_parser_error_quark :
*
* Registers an error quark for VFL error parsing .
*
* Returns : the error quark
**/
GQuark
gtk_constraint_vfl_parser_error_quark (void )
{
return g_quark_from_static_string ("gtk-constraint-vfl-parser-error-quark" );
}
/**
* gtk_constraint_layout_add_constraints_from_descriptionv : ( rename - to gtk_constraint_layout_add_constraints_from_description )
* @ layout : a ` GtkConstraintLayout `
* @ lines : ( array length = n_lines ) : an array of Visual Format Language lines
* defining a set of constraints
* @ n_lines : the number of lines
* @ hspacing : default horizontal spacing value , or - 1 for the fallback value
* @ vspacing : default vertical spacing value , or - 1 for the fallback value
* @ views : ( element - type utf8 Gtk . ConstraintTarget ) : a dictionary of ` [ name , target ] `
* pairs ; the ` name ` keys map to the view names in the VFL lines , while
* the ` target ` values map to children of the widget using a ` GtkConstraintLayout ` ,
* or guides
* @ error : return location for a ` GError `
*
* Creates a list of constraints from a VFL description .
*
* The Visual Format Language , VFL , is based on Apple ' s AutoLayout [ VFL ] ( https : //developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/VisualFormatLanguage.html).
*
* The ` views ` dictionary is used to match [ iface @ Gtk . ConstraintTarget ]
* instances to the symbolic view name inside the VFL .
*
* The VFL grammar is :
*
* ` ` `
* < visualFormatString > = ( < orientation > ) ?
* ( < superview > < connection > ) ?
* < view > ( < connection > < view > ) *
* ( < connection > < superview > ) ?
* < orientation > = ' H ' | ' V '
* < superview > = ' | '
* < connection > = ' ' | ' - ' < predicateList > ' - ' | ' - '
* < predicateList > = < simplePredicate > | < predicateListWithParens >
* < simplePredicate > = < metricName > | < positiveNumber >
* < predicateListWithParens > = ' ( ' < predicate > ( ' , ' < predicate > ) * ' ) '
* < predicate > = ( < relation > ) ? < objectOfPredicate > ( < operatorList > ) ? ( ' @ ' < priority > ) ?
* < relation > = ' = = ' | ' < = ' | ' > = '
* < objectOfPredicate > = < constant > | < viewName > | ( ' . ' < attributeName > ) ?
* < priority > = < positiveNumber > | ' required ' | ' strong ' | ' medium ' | ' weak '
* < constant > = < number >
* < operatorList > = ( < multiplyOperator > ) ? ( < addOperator > ) ?
* < multiplyOperator > = [ ' * ' | ' / ' ] < positiveNumber >
* < addOperator > = [ ' + ' | ' - ' ] < positiveNumber >
* < viewName > = [ A - Za - z_ ] ( [ A - Za - z0 - 9 _ ] * ) // A C identifier
* < metricName > = [ A - Za - z_ ] ( [ A - Za - z0 - 9 _ ] * ) // A C identifier
* < attributeName > = ' top ' | ' bottom ' | ' left ' | ' right ' | ' width ' | ' height ' |
* ' start ' | ' end ' | ' centerX ' | ' centerY ' | ' baseline '
* < positiveNumber > // A positive real number parseable by g_ascii_strtod()
* < number > // A real number parseable by g_ascii_strtod()
* ` ` `
*
* * * Note * * : The VFL grammar used by GTK is slightly different than the one
* defined by Apple , as it can use symbolic values for the constraint ' s
* strength instead of numeric values ; additionally , GTK allows adding
* simple arithmetic operations inside predicates .
*
* Examples of VFL descriptions are :
*
* ` ` `
* // Default spacing
* [ button ] - [ textField ]
*
* // Width constraint
* [ button ( > = 50 ) ]
*
* // Connection to super view
* | - 50 - [ purpleBox ] - 50 - |
*
* // Vertical layout
* V : [ topField ] - 10 - [ bottomField ]
*
* // Flush views
* [ maroonView ] [ blueView ]
*
* // Priority
* [ button ( 100 @ strong ) ]
*
* // Equal widths
* [ button1 ( = = button2 ) ]
*
* // Multiple predicates
* [ flexibleButton ( > = 70 , < = 100 ) ]
*
* // A complete line of layout
* | - [ find ] - [ findNext ] - [ findField ( > = 20 ) ] - |
*
* // Operators
* [ button1 ( button2 / 3 + 50 ) ]
*
* // Named attributes
* [ button1 ( = = button2 . height ) ]
* ` ` `
*
* Returns : ( transfer container ) ( element - type GtkConstraint ) : the list of
* [ class @ Gtk . Constraint ] instances that were added to the layout
*/
GList *
gtk_constraint_layout_add_constraints_from_descriptionv (GtkConstraintLayout *layout,
const char * const lines[],
gsize n_lines,
int hspacing,
int vspacing,
GHashTable *views,
GError **error)
{
GtkConstraintVflParser *parser;
GList *res = NULL;
g_return_val_if_fail (GTK_IS_CONSTRAINT_LAYOUT (layout), NULL);
g_return_val_if_fail (lines != NULL, NULL);
g_return_val_if_fail (views != NULL, NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
parser = gtk_constraint_vfl_parser_new ();
gtk_constraint_vfl_parser_set_default_spacing (parser, hspacing, vspacing);
gtk_constraint_vfl_parser_set_views (parser, views);
for (gsize i = 0 ; i < n_lines; i++)
{
const char *line = lines[i];
GError *internal_error = NULL;
gtk_constraint_vfl_parser_parse_line (parser, line, -1 , &internal_error);
if (internal_error != NULL)
{
int offset = gtk_constraint_vfl_parser_get_error_offset (parser);
int range = gtk_constraint_vfl_parser_get_error_range (parser);
char *squiggly = NULL;
if (range > 0 )
squiggly = g_strnfill (range, '~' );
g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
internal_error->code,
"%" G_GSIZE_FORMAT ":%d: %s\n"
"%s\n"
"%*s^%s" ,
i, offset + 1 ,
internal_error->message,
line,
offset, " " , squiggly != NULL ? squiggly : "" );
g_free (squiggly);
g_error_free (internal_error);
gtk_constraint_vfl_parser_free (parser);
return res;
}
int n_constraints = 0 ;
GtkConstraintVfl *constraints = gtk_constraint_vfl_parser_get_constraints (parser, &n_constraints);
for (int j = 0 ; j < n_constraints; j++)
{
const GtkConstraintVfl *c = &constraints[j];
gpointer source, target;
GtkConstraintAttribute source_attr, target_attr;
target = g_hash_table_lookup (views, c->view1);
target_attr = attribute_from_name (c->attr1);
if (c->view2 != NULL)
source = g_hash_table_lookup (views, c->view2);
else
source = NULL;
if (c->attr2 != NULL)
source_attr = attribute_from_name (c->attr2);
else
source_attr = GTK_CONSTRAINT_ATTRIBUTE_NONE;
GtkConstraint *constraint =
gtk_constraint_new (target, target_attr,
c->relation,
source, source_attr,
c->multiplier,
c->constant,
c->strength);
layout_add_constraint (layout, constraint);
g_hash_table_add (layout->constraints, constraint);
if (layout->constraints_observer)
g_list_store_append (layout->constraints_observer, constraint);
res = g_list_prepend (res, constraint);
}
g_free (constraints);
}
gtk_constraint_vfl_parser_free (parser);
gtk_layout_manager_layout_changed (GTK_LAYOUT_MANAGER (layout));
return res;
}
/**
* gtk_constraint_layout_add_constraints_from_description :
* @ layout : a ` GtkConstraintLayout `
* @ lines : ( array length = n_lines ) : an array of Visual Format Language lines
* defining a set of constraints
* @ n_lines : the number of lines
* @ hspacing : default horizontal spacing value , or - 1 for the fallback value
* @ vspacing : default vertical spacing value , or - 1 for the fallback value
* @ error : return location for a ` GError `
* @ first_view : the name of a view in the VFL description , followed by the
* [ iface @ Gtk . ConstraintTarget ] to which it maps
* @ . . . : a ` NULL ` - terminated list of view names and [ iface @ Gtk . ConstraintTarget ] s
*
* Creates a list of constraints from a VFL description .
*
* This function is a convenience wrapper around
* [ method @ Gtk . ConstraintLayout . add_constraints_from_descriptionv ] , using
* variadic arguments to populate the view / target map .
*
* Returns : ( transfer container ) ( element - type Gtk . Constraint ) : the list of
* [ class @ Gtk . Constraint ] s that were added to the layout
*/
GList *
gtk_constraint_layout_add_constraints_from_description (GtkConstraintLayout *layout,
const char * const lines[],
gsize n_lines,
int hspacing,
int vspacing,
GError **error,
const char *first_view,
...)
{
GtkConstraintVflParser *parser;
GHashTable *views;
const char *view;
GList *res;
va_list args;
g_return_val_if_fail (GTK_IS_CONSTRAINT_LAYOUT (layout), NULL);
g_return_val_if_fail (lines != NULL, NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
g_return_val_if_fail (first_view != NULL, NULL);
parser = gtk_constraint_vfl_parser_new ();
gtk_constraint_vfl_parser_set_default_spacing (parser, hspacing, vspacing);
views = g_hash_table_new (g_str_hash, g_str_equal);
va_start (args, first_view);
view = first_view;
while (view != NULL)
{
GtkConstraintTarget *target = va_arg (args, GtkConstraintTarget *);
if (target == NULL)
break ;
g_hash_table_insert (views, (gpointer) view, target);
view = va_arg (args, const char *);
}
va_end (args);
res =
gtk_constraint_layout_add_constraints_from_descriptionv (layout, lines, n_lines,
hspacing, vspacing,
views,
error);
g_hash_table_unref (views);
return res;
}
/**
* gtk_constraint_layout_observe_constraints :
* @ layout : a ` GtkConstraintLayout `
*
* Returns a ` GListModel ` to track the constraints that are
* part of the layout .
*
* Calling this function will enable extra internal bookkeeping
* to track constraints and emit signals on the returned listmodel .
* It may slow down operations a lot .
*
* Applications should try hard to avoid calling this function
* because of the slowdowns .
*
* Returns : ( transfer full ) ( attributes element - type = GtkConstraint ) : a
* ` GListModel ` tracking the layout ' s constraints
*/
GListModel *
gtk_constraint_layout_observe_constraints (GtkConstraintLayout *layout)
{
GHashTableIter iter;
gpointer key;
if (layout->constraints_observer)
return g_object_ref (G_LIST_MODEL (layout->constraints_observer));
layout->constraints_observer = g_list_store_new (GTK_TYPE_CONSTRAINT);
g_object_add_weak_pointer ((GObject *)layout->constraints_observer,
(gpointer *)&layout->constraints_observer);
g_hash_table_iter_init (&iter, layout->constraints);
while (g_hash_table_iter_next (&iter, &key, NULL))
{
GtkConstraint *constraint = key;
g_list_store_append (layout->constraints_observer, constraint);
}
return G_LIST_MODEL (layout->constraints_observer);
}
/**
* gtk_constraint_layout_observe_guides :
* @ layout : a ` GtkConstraintLayout `
*
* Returns a ` GListModel ` to track the guides that are
* part of the layout .
*
* Calling this function will enable extra internal bookkeeping
* to track guides and emit signals on the returned listmodel .
* It may slow down operations a lot .
*
* Applications should try hard to avoid calling this function
* because of the slowdowns .
*
* Returns : ( transfer full ) ( attributes element - type = GtkConstraintGuide ) : a
* ` GListModel ` tracking the layout ' s guides
*/
GListModel *
gtk_constraint_layout_observe_guides (GtkConstraintLayout *layout)
{
GHashTableIter iter;
gpointer key;
if (layout->guides_observer)
return g_object_ref (G_LIST_MODEL (layout->guides_observer));
layout->guides_observer = g_list_store_new (GTK_TYPE_CONSTRAINT_GUIDE);
g_object_add_weak_pointer ((GObject *)layout->guides_observer,
(gpointer *)&layout->guides_observer);
g_hash_table_iter_init (&iter, layout->guides);
while (g_hash_table_iter_next (&iter, &key, NULL))
{
GtkConstraintGuide *guide = key;
g_list_store_append (layout->guides_observer, guide);
}
return G_LIST_MODEL (layout->guides_observer);
}
Messung V0.5 in Prozent C=98 H=95 G=96
¤ Dauer der Verarbeitung: 0.77 Sekunden
(vorverarbeitet am 2026-07-02)
¤
*© Formatika GbR, Deutschland