diff options
author | Emmanuele Bassi <ebassi@gnome.org> | 2019-06-29 18:32:56 +0100 |
---|---|---|
committer | Emmanuele Bassi <ebassi@gnome.org> | 2019-06-29 18:50:43 +0100 |
commit | 6be2a787e0466cf5ec3a3a9ef534d255b239b330 (patch) | |
tree | 4bb7640fcf9775c56637cc4b62254fade96d39d9 | |
parent | 1e725b66a5d1031f87f687c689724a8ab920a1b0 (diff) | |
download | gtk+-6be2a787e0466cf5ec3a3a9ef534d255b239b330.tar.gz |
Allow adding constraints described through VFL
-rw-r--r-- | gtk/gtkconstraintlayout.c | 219 | ||||
-rw-r--r-- | gtk/gtkconstraintlayout.h | 9 |
2 files changed, 227 insertions, 1 deletions
diff --git a/gtk/gtkconstraintlayout.c b/gtk/gtkconstraintlayout.c index 405d080164..25f5f8fcd8 100644 --- a/gtk/gtkconstraintlayout.c +++ b/gtk/gtkconstraintlayout.c @@ -64,9 +64,10 @@ #include "gtkconstraintprivate.h" #include "gtkconstraintexpressionprivate.h" #include "gtkconstraintsolverprivate.h" -#include "gtklayoutchild.h" +#include "gtkconstraintvflparserprivate.h" #include "gtkdebug.h" +#include "gtklayoutchild.h" #include "gtkintl.h" #include "gtkprivate.h" #include "gtksizerequest.h" @@ -1585,3 +1586,219 @@ gtk_constraint_layout_remove_guide (GtkConstraintLayout *layout, 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_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.Widget): a dictionary of [ name, widget ] + * pairs; the `name` keys map to the view names in the VFL lines, while + * the `widget` values map to children of the widget using a #GtkConstraintLayout + * + * Creates a list of constraints they formal description using a compact + * description syntax called VFL, or "Visual Format Language". + * + * The Visual Format Language 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 widgets to the symbolic view name + * inside the VFL. + * + * The VFL grammar is: + * + * |[<!-- language="plain" --> + * <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: + * + * |[<!-- language="plain" --> + * // 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: %TRUE if the constraints were added to the layout + */ +gboolean +gtk_constraint_layout_add_constraints_from_description (GtkConstraintLayout *layout, + const char * const lines[], + gsize n_lines, + int hspacing, + int vspacing, + GHashTable *views, + GError **error) +{ + GtkConstraintVflParser *parser; + + g_return_val_if_fail (GTK_IS_CONSTRAINT_LAYOUT (layout), FALSE); + g_return_val_if_fail (lines != NULL, FALSE); + g_return_val_if_fail (views != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + 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_new (char, range + 1); + + for (int r = 0; r < range; i++) + squiggly[r] = '~'; + + squiggly[range] = '\0'; + } + + 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 FALSE; + } + + 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); + } + + g_free (constraints); + } + + gtk_constraint_vfl_parser_free (parser); + + return TRUE; +} diff --git a/gtk/gtkconstraintlayout.h b/gtk/gtkconstraintlayout.h index 758fcefb20..39d3aecaf0 100644 --- a/gtk/gtkconstraintlayout.h +++ b/gtk/gtkconstraintlayout.h @@ -76,4 +76,13 @@ GDK_AVAILABLE_IN_ALL void gtk_constraint_layout_remove_guide (GtkConstraintLayout *manager, GtkConstraintGuide *guide); +GDK_AVAILABLE_IN_ALL +gboolean gtk_constraint_layout_add_constraints_from_description (GtkConstraintLayout *manager, + const char * const lines[], + gsize n_lines, + int hspacing, + int vspacing, + GHashTable *views, + GError **error); + G_END_DECLS |