summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEmmanuele Bassi <ebassi@gnome.org>2019-06-29 18:32:56 +0100
committerEmmanuele Bassi <ebassi@gnome.org>2019-06-29 18:50:43 +0100
commit6be2a787e0466cf5ec3a3a9ef534d255b239b330 (patch)
tree4bb7640fcf9775c56637cc4b62254fade96d39d9
parent1e725b66a5d1031f87f687c689724a8ab920a1b0 (diff)
downloadgtk+-6be2a787e0466cf5ec3a3a9ef534d255b239b330.tar.gz
Allow adding constraints described through VFL
-rw-r--r--gtk/gtkconstraintlayout.c219
-rw-r--r--gtk/gtkconstraintlayout.h9
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