diff --git a/shoes/layout/emeus-constraint-layout-private.h b/shoes/layout/emeus-constraint-layout-private.h new file mode 100644 index 00000000..b2b11e83 --- /dev/null +++ b/shoes/layout/emeus-constraint-layout-private.h @@ -0,0 +1,104 @@ +/* emeus-constraint-layout-private.h: The constraint layout manager + * + * Copyright 2016 Endless + * + * 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 . + */ + +#pragma once + +#include "emeus-constraint-layout.h" +#include "emeus-types-private.h" +#include "shoes/layout/gshoes-ele.h" + +G_BEGIN_DECLS + +struct _EmeusConstraintLayoutChild +{ + //GtkBin parent_instance; + GshoesEle *parent_instance; + + /* An optional name for the child, to be used when building + * constraints from string descriptions + */ + char *name; + + /* Position in the layout's GSequence */ + GSequenceIter *iter; + + /* Back pointer to the solver in the layout */ + SimplexSolver *solver; + + /* HashTable; 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; the set of constraints on the + * widget, using the public API objects. + */ + GHashTable *constraints; + + double intrinsic_width; + double intrinsic_height; + + /* Internal constraints, created to satisfy specific bound + * attributes; may be unset. + */ + Constraint *right_constraint; + Constraint *bottom_constraint; + Constraint *center_x_constraint; + Constraint *center_y_constraint; + Constraint *width_constraint; + Constraint *height_constraint; +}; + +struct _EmeusConstraintLayout +{ + //GtkContainer parent_instance; + void * parent_instance; // points to shoes_layout struct + + GSequence *children; + + SimplexSolver solver; + + GHashTable *bound_attributes; + + /* HashSet; the set of constraints on the + * widget, using the public API objects. + */ + GHashTable *constraints; + + /* Internal constraints */ + struct { + Constraint *top; + Constraint *left; + Constraint *width; + Constraint *height; + } stays; +}; + +SimplexSolver * emeus_constraint_layout_get_solver (EmeusConstraintLayout *layout); + +gboolean emeus_constraint_layout_has_child_data (EmeusConstraintLayout *layout, + GshoesEle *widget); + +void emeus_constraint_layout_activate_constraint (EmeusConstraintLayout *layout, + EmeusConstraint *constraint); +void emeus_constraint_layout_deactivate_constraint (EmeusConstraintLayout *layout, + EmeusConstraint *constraint); + +G_END_DECLS diff --git a/shoes/layout/emeus-constraint-layout.c b/shoes/layout/emeus-constraint-layout.c new file mode 100644 index 00000000..b516df28 --- /dev/null +++ b/shoes/layout/emeus-constraint-layout.c @@ -0,0 +1,2582 @@ +/* emeus-constraint-layout.c: The constraint layout manager + * + * Copyright 2016 Endless + * + * 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 . + */ + + /* + * Modified for Shoes usage, November 2018 by Cecil Coupe. + * Removed builder interface. + * Use Shoes instead of Gtk. + */ + +/** + * SECTION:emeus-constraint-layout + * @Title: EmeusConstraintLayout + * @Short_Description: A widget container using layout constraints + * + * #EmeusConstraintLayout is a #GtkContainter that uses constraints + * associated to each of its children to decide their layout. + * + * # How do constraints work + * + * Constraints are objects defining the relationship between attributes + * of a widget; you can read the description of the #EmeusConstraint + * class to have a more in depth definition. + * + * By taking multiple constraints and applying them to the children of + * an #EmeusConstraintLayout, it's possible to describe complex layout + * policies; each constraint applied to a child or to the layout itself + * 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 a layout that + * cannot be solved. + * + * 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. + * + * # Adding and removing widgets + * + * #EmeusConstraintLayout accepts only one type of widget as its direct + * child, #EmeusConstraintLayoutChild. As a convenience for the developer, + * #EmeusConstraintLayout will automatically add an #EmeusConstraintLayoutChild + * for any other widget type when calling emeus_constraint_layout_pack() or + * gtk_container_add(). + * + * Similarly, when removing a #GtkWidget from an #EmeusConstraintLayout, + * the container will remove the intermediate #EmeusConstraintLayoutChild + * that was added automatically. + * + * When iterating over the children of #EmeusConstraintLayout, you will + * get only #EmeusConstraintLayoutChild instances; use gtk_bin_get_child() + * to retrieve the grandchild of the layout. + * + * # Describing constraints using GtkBuilder + * + * When creating an #EmeusConstraintLayout layout with GtkBuilder, it + * can be simpler to also load constraints directly from the UI definition + * file. + * + * #EmeusConstraintLayout implements the #GtkBuildable interface, and + * adds the `constraints` tag, which contains `constraint` elements. + * + * The `constraint` element has various attributes: + * + * - `target-object`, used to point to a widget using its builder id; this + * attribute is mandatory, and specifies the value of the + * #EmeusConstraint:target-object property + * - `target-attr`, which contains an %EmeusConstraintAttribute value; this + * attribute is mandatory, and specifies the value of the + * #EmeusConstraint:target-attribute property + * - `source-object`, used to point to a widget using its builder id; this + * attribute is optional, for constant constraints, and specifies the value + * of the #EmeusConstraint:source-object property + * - `source-attr`, which contains an %EmeusConstraintAttribute value; this + * attribute is optional, for constant constraints, and specifies the value + * of the #EmeusConstraint:source-attribute property + * - `relation`, which contains an %EmeusConstraintRelation value; this + * attribute is optional, and if not found the relation will be + * %EMEUS_CONSTRAINT_RELATION_EQ + * - `constant`, which contains the constant factor value as a floating point + * numer; this attribute is optional, and if not found the constant will + * be set to 0 + * - `multiplier`, which contains the multiplication factor value as a + * floating point number; this attribute is option, and if not found the + * multiplier will be set to 1 + * - `strength`, which contains an %EmeusConstraintStrength value, or a positive + * integer; this attribute is optional, and if not found the strength will + * be %EMEUS_CONSTRAINT_STRENGTH_REQUIRED + * + * A simple layout definitions is: + * + * |[ + * + * True + * + * + * ... + * + * + * + * + * ... + * + * + * + * + * + * ... + * + * + * ]| + * + * # Describing constraints using the Visual Format Language + * + * While it's entirely possible to describe layouts by writing constraints + * by hand, it can be tedious to do so. Additionally, writing constraints + * by hand may lead to forgetting one or more of them, thus causing the + * layout to be unstable or incomplete; or to add conflicting constraints, + * thus causing the layout to be unsolvable. + * + * In order to quickly visualize, and generate, constraints for a layout, + * Emeus implements a Visual Format language. Each row and column of a + * layout can be described by the VFL, and translated into constraints to + * be applied on top of a #EmeusConstraintLayout and its children. Once + * the layout has been established, it's also possible to impose new + * constraints for additional layout rules. + * + * A simple VFL description of a layout is: + * + * |[ + * |-[view0]-[view1(view0)]-| + * [view2(view1)]-| + * V:|-[view0]-| + * V:|-[view1]-[view2(view1)]-| + * ]| + * + * The description will generate a layout composed by three widgets arrange + * in two columns: one with 'view0', and the other with 'view1' and 'view2'. + * The 'view1' and 'view2' widgets will have the same width and height, and + * all three widgets will grow to fill the available space if their parent + * grows. + * + * See emeus_create_constraints_from_description() for the VFL grammar and + * additional examples of layouts. + */ + +#include "config.h" + +#include "emeus-constraint-layout-private.h" + +#include "emeus-constraint-private.h" +#include "emeus-types-private.h" +#include "emeus-expression-private.h" +#include "emeus-simplex-solver-private.h" +#include "emeus-utils-private.h" + +#include +#include +#include + +enum { + CHILD_PROP_NAME = 1, + + CHILD_N_PROPS +}; + +static GParamSpec *emeus_constraint_layout_child_properties[CHILD_N_PROPS]; + +#if 0 +static GtkBuildableIface *parent_buildable_iface; + +static GQuark quark_buildable_constraints; + +static void emeus_constraint_layout_buildable_iface_init (GtkBuildableIface *iface); + +G_DEFINE_TYPE_WITH_CODE (EmeusConstraintLayout, emeus_constraint_layout, GTK_TYPE_CONTAINER, + G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, emeus_constraint_layout_buildable_iface_init)) + + +G_DEFINE_TYPE (EmeusConstraintLayoutChild, emeus_constraint_layout_child, GTK_TYPE_BIN) +#else + +G_DEFINE_TYPE (EmeusConstraintLayout, emeus_constraint_layout, G_TYPE_OBJECT); + +G_DEFINE_TYPE (EmeusConstraintLayoutChild, emeus_constraint_layout_child, G_TYPE_OBJECT) +#endif + +#ifdef EMEUS_ENABLE_DEBUG +# define DEBUG(x) x +#else +# define DEBUG(x) +#endif + +static void +emeus_constraint_layout_finalize (GObject *gobject) +{ + EmeusConstraintLayout *self = EMEUS_CONSTRAINT_LAYOUT (gobject); + + g_clear_pointer (&self->children, g_sequence_free); + g_clear_pointer (&self->bound_attributes, g_hash_table_unref); + g_clear_pointer (&self->constraints, g_hash_table_unref); + + simplex_solver_remove_constraint (&self->solver, self->stays.top); + simplex_solver_remove_constraint (&self->solver, self->stays.left); + simplex_solver_remove_constraint (&self->solver, self->stays.width); + simplex_solver_remove_constraint (&self->solver, self->stays.height); + + simplex_solver_clear (&self->solver); + + G_OBJECT_CLASS (emeus_constraint_layout_parent_class)->finalize (gobject); +} + +// TODO: cjc +static void +emeus_constraint_layout_destroy (GshoesEle *widget) +{ + EmeusConstraintLayout *self = EMEUS_CONSTRAINT_LAYOUT (widget); + + simplex_solver_freeze (&self->solver); + + //GTK_WIDGET_CLASS (emeus_constraint_layout_parent_class)->destroy (widget); +} + +static Variable * +get_layout_attribute (EmeusConstraintLayout *layout, + EmeusConstraintAttribute attr) +{ +#if 0 + GtkTextDirection text_dir; + + /* Resolve the start/end attributes depending on the layout's text direction */ + if (attr == EMEUS_CONSTRAINT_ATTRIBUTE_START) + { + text_dir = gtk_widget_get_direction (GTK_WIDGET (layout)); + if (text_dir == GTK_TEXT_DIR_RTL) + attr = EMEUS_CONSTRAINT_ATTRIBUTE_RIGHT; + else + attr = EMEUS_CONSTRAINT_ATTRIBUTE_LEFT; + } + else if (attr == EMEUS_CONSTRAINT_ATTRIBUTE_END) + { + text_dir = gtk_widget_get_direction (GTK_WIDGET (layout)); + if (text_dir == GTK_TEXT_DIR_RTL) + attr = EMEUS_CONSTRAINT_ATTRIBUTE_LEFT; + else + attr = EMEUS_CONSTRAINT_ATTRIBUTE_RIGHT; + } +#endif + + const char *attr_name = get_attribute_name (attr); + Variable *res = g_hash_table_lookup (layout->bound_attributes, attr_name); + if (res != NULL) + return res; + + res = simplex_solver_create_variable (&layout->solver, attr_name, 0.0); + variable_set_prefix (res, "super"); + + g_hash_table_insert (layout->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 EMEUS_CONSTRAINT_ATTRIBUTE_RIGHT: + { + Variable *left = get_layout_attribute (layout, EMEUS_CONSTRAINT_ATTRIBUTE_LEFT); + Variable *width = get_layout_attribute (layout, EMEUS_CONSTRAINT_ATTRIBUTE_WIDTH); + Expression *expr = + expression_plus_variable (expression_new_from_variable (left), width); + + simplex_solver_add_constraint (&layout->solver, + res, OPERATOR_TYPE_EQ, expr, + STRENGTH_MEDIUM); + + expression_unref (expr); + } + break; + + /* bottom = top + height */ + case EMEUS_CONSTRAINT_ATTRIBUTE_BOTTOM: + { + Variable *top = get_layout_attribute (layout, EMEUS_CONSTRAINT_ATTRIBUTE_TOP); + Variable *height = get_layout_attribute (layout, EMEUS_CONSTRAINT_ATTRIBUTE_HEIGHT); + Expression *expr = + expression_plus_variable (expression_new_from_variable (top), height); + + simplex_solver_add_constraint (&layout->solver, + res, OPERATOR_TYPE_EQ, expr, + STRENGTH_MEDIUM); + + expression_unref (expr); + } + break; + + /* centerX = left + (width / 2.0) */ + case EMEUS_CONSTRAINT_ATTRIBUTE_CENTER_X: + { + Variable *left = get_layout_attribute (layout, EMEUS_CONSTRAINT_ATTRIBUTE_LEFT); + Variable *width = get_layout_attribute (layout, EMEUS_CONSTRAINT_ATTRIBUTE_WIDTH); + Expression *expr = + expression_plus_variable (expression_divide (expression_new_from_variable (width), 2.0), left); + + simplex_solver_add_constraint (&layout->solver, + res, OPERATOR_TYPE_EQ, expr, + STRENGTH_REQUIRED); + + expression_unref (expr); + } + break; + + /* centerY = top + (height / 2.0) */ + case EMEUS_CONSTRAINT_ATTRIBUTE_CENTER_Y: + { + Variable *top = get_layout_attribute (layout, EMEUS_CONSTRAINT_ATTRIBUTE_TOP); + Variable *height = get_layout_attribute (layout, EMEUS_CONSTRAINT_ATTRIBUTE_HEIGHT); + Expression *expr = + expression_plus_variable (expression_divide (expression_new_from_variable (height), 2.0), top); + + simplex_solver_add_constraint (&layout->solver, + res, OPERATOR_TYPE_EQ, expr, + STRENGTH_REQUIRED); + + expression_unref (expr); + } + break; + + case EMEUS_CONSTRAINT_ATTRIBUTE_WIDTH: + case EMEUS_CONSTRAINT_ATTRIBUTE_HEIGHT: + { + Expression *expr = expression_new_from_constant (0.0); + simplex_solver_add_constraint (&layout->solver, + res, OPERATOR_TYPE_GE, expr, + STRENGTH_REQUIRED); + + expression_unref (expr); + } + break; + + default: + break; + } + + return res; +} + +static Variable * +get_child_attribute (EmeusConstraintLayoutChild *child, + EmeusConstraintAttribute attr) +{ +#if 0 + GtkTextDirection text_dir; + + /* Resolve the start/end attributes depending on the child's text direction */ + if (attr == EMEUS_CONSTRAINT_ATTRIBUTE_START) + { + text_dir = gtk_widget_get_direction (GTK_WIDGET (child)); + if (text_dir == GTK_TEXT_DIR_RTL) + attr = EMEUS_CONSTRAINT_ATTRIBUTE_RIGHT; + else + attr = EMEUS_CONSTRAINT_ATTRIBUTE_LEFT; + } + else if (attr == EMEUS_CONSTRAINT_ATTRIBUTE_END) + { + text_dir = gtk_widget_get_direction (GTK_WIDGET (child)); + if (text_dir == GTK_TEXT_DIR_RTL) + attr = EMEUS_CONSTRAINT_ATTRIBUTE_LEFT; + else + attr = EMEUS_CONSTRAINT_ATTRIBUTE_RIGHT; + } +#endif + const char *attr_name = get_attribute_name (attr); + Variable *res = g_hash_table_lookup (child->bound_attributes, attr_name); + if (res != NULL) + return res; + + res = simplex_solver_create_variable (child->solver, attr_name, 0.0); + variable_set_prefix (res, child->name); + + g_hash_table_insert (child->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 EMEUS_CONSTRAINT_ATTRIBUTE_RIGHT: + { + Variable *left = get_child_attribute (child, EMEUS_CONSTRAINT_ATTRIBUTE_LEFT); + Variable *width = get_child_attribute (child, EMEUS_CONSTRAINT_ATTRIBUTE_WIDTH); + Expression *expr = + expression_plus_variable (expression_new_from_variable (left), width); + + child->right_constraint = + simplex_solver_add_constraint (child->solver, + res, OPERATOR_TYPE_EQ, expr, + STRENGTH_MEDIUM); + + expression_unref (expr); + } + break; + + /* bottom = top + height */ + case EMEUS_CONSTRAINT_ATTRIBUTE_BOTTOM: + { + Variable *top = get_child_attribute (child, EMEUS_CONSTRAINT_ATTRIBUTE_TOP); + Variable *height = get_child_attribute (child, EMEUS_CONSTRAINT_ATTRIBUTE_HEIGHT); + Expression *expr = + expression_plus_variable (expression_new_from_variable (top), height); + + child->bottom_constraint = + simplex_solver_add_constraint (child->solver, + res, OPERATOR_TYPE_EQ, expr, + STRENGTH_MEDIUM); + + expression_unref (expr); + } + break; + + /* centerX = left + (width / 2.0) */ + case EMEUS_CONSTRAINT_ATTRIBUTE_CENTER_X: + { + Variable *left = get_child_attribute (child, EMEUS_CONSTRAINT_ATTRIBUTE_LEFT); + Variable *width = get_child_attribute (child, EMEUS_CONSTRAINT_ATTRIBUTE_WIDTH); + Expression *expr = + expression_plus_variable (expression_divide (expression_new_from_variable (width), 2.0), left); + + child->center_x_constraint = + simplex_solver_add_constraint (child->solver, + res, OPERATOR_TYPE_EQ, expr, + STRENGTH_REQUIRED); + + expression_unref (expr); + } + break; + + /* centerY = top + (height / 2.0) */ + case EMEUS_CONSTRAINT_ATTRIBUTE_CENTER_Y: + { + Variable *top = get_child_attribute (child, EMEUS_CONSTRAINT_ATTRIBUTE_TOP); + Variable *height = get_child_attribute (child, EMEUS_CONSTRAINT_ATTRIBUTE_HEIGHT); + Expression *expr = + expression_plus_variable (expression_divide (expression_new_from_variable (height), 2.0), top); + + child->center_y_constraint = + simplex_solver_add_constraint (child->solver, + res, OPERATOR_TYPE_EQ, expr, + STRENGTH_REQUIRED); + + expression_unref (expr); + } + break; + + case EMEUS_CONSTRAINT_ATTRIBUTE_WIDTH: + case EMEUS_CONSTRAINT_ATTRIBUTE_HEIGHT: + { + Expression *expr = expression_new_from_constant (0.0); + simplex_solver_add_constraint (child->solver, + res, OPERATOR_TYPE_GE, expr, + STRENGTH_REQUIRED); + + expression_unref (expr); + } + break; + + default: + break; + } + + return res; +} + +static void +add_layout_stays (EmeusConstraintLayout *self) +{ + Variable *var; + + /* Add two required stay constraints for the top left corner */ + var = simplex_solver_create_variable (&self->solver, "top", 0.0); + variable_set_prefix (var, "super"); + g_hash_table_insert (self->bound_attributes, + (gpointer) get_attribute_name (EMEUS_CONSTRAINT_ATTRIBUTE_TOP), + var); + self->stays.top = + simplex_solver_add_stay_variable (&self->solver, var, STRENGTH_WEAK); + + var = simplex_solver_create_variable (&self->solver, "left", 0.0); + variable_set_prefix (var, "super"); + g_hash_table_insert (self->bound_attributes, + (gpointer) get_attribute_name (EMEUS_CONSTRAINT_ATTRIBUTE_LEFT), + var); + self->stays.left = + simplex_solver_add_stay_variable (&self->solver, var, STRENGTH_WEAK); + + var = simplex_solver_create_variable (&self->solver, "width", 0.0); + variable_set_prefix (var, "super"); + g_hash_table_insert (self->bound_attributes, + (gpointer) get_attribute_name (EMEUS_CONSTRAINT_ATTRIBUTE_WIDTH), + var); + self->stays.width = + simplex_solver_add_stay_variable (&self->solver, var, STRENGTH_WEAK); + + var = simplex_solver_create_variable (&self->solver, "height", 0.0); + variable_set_prefix (var, "super"); + g_hash_table_insert (self->bound_attributes, + (gpointer) get_attribute_name (EMEUS_CONSTRAINT_ATTRIBUTE_HEIGHT), + var); + self->stays.height = + simplex_solver_add_stay_variable (&self->solver, var, STRENGTH_WEAK); +} + +#if 0 +static void +emeus_constraint_layout_get_preferred_size (EmeusConstraintLayout *self, + GtkOrientation orientation, + int for_size, + int *minimum_p, + int *natural_p) +{ + Variable *size = NULL; + Variable *opposite_size = NULL; + + if (g_sequence_is_empty (self->children)) + { + if (minimum_p != NULL) + *minimum_p = 0; + + if (natural_p != NULL) + *natural_p = 0; + + return; + } + + switch (orientation) + { + case GTK_ORIENTATION_HORIZONTAL: + size = get_layout_attribute (self, EMEUS_CONSTRAINT_ATTRIBUTE_WIDTH); + opposite_size = get_layout_attribute (self, EMEUS_CONSTRAINT_ATTRIBUTE_HEIGHT); + break; + + case GTK_ORIENTATION_VERTICAL: + size = get_layout_attribute (self, EMEUS_CONSTRAINT_ATTRIBUTE_HEIGHT); + opposite_size = get_layout_attribute (self, EMEUS_CONSTRAINT_ATTRIBUTE_WIDTH); + break; + } + + g_assert (size != NULL); + g_assert (opposite_size != NULL); + + /* We impose new temporary stay constraints on the size and its opposite, + * with a low priority so that the solver will revert to the preferred + * size of the layout for the duration of this function. + * + * The strength is (WEAK + 1) because it has to override the WEAK strength + * stay constraints we add to the layout inside the instance initialization + * function, whose job is to keep the layout origin and size greather than + * or equal to zero. If we used the same priority, the solver would be in + * an unstable state, and randomly fall back to a preferred size of 0. + */ + variable_set_value (size, 0.0); + + Constraint *stay_s = + simplex_solver_add_stay_variable (&self->solver, size, STRENGTH_WEAK + 1); + + variable_set_value (opposite_size, for_size > 0 ? for_size : 0.0); + + Constraint *stay_o = + simplex_solver_add_stay_variable (&self->solver, opposite_size, STRENGTH_WEAK + 1); + + DEBUG (g_debug ("layout %p preferred %s size: %.3f (for opposite size: %d)", + self, + orientation == GTK_ORIENTATION_HORIZONTAL ? "horizontal" : "vertical", + variable_get_value (size), + for_size)); + + double value = variable_get_value (size); + + simplex_solver_remove_constraint (&self->solver, stay_s); + simplex_solver_remove_constraint (&self->solver, stay_o); + + if (minimum_p != NULL) + *minimum_p = value; + if (natural_p != NULL) + *natural_p = value; +} + +static void +emeus_constraint_layout_get_preferred_width (GtkWidget *widget, + int *minimum_p, + int *natural_p) +{ + emeus_constraint_layout_get_preferred_size (EMEUS_CONSTRAINT_LAYOUT (widget), + GTK_ORIENTATION_HORIZONTAL, + -1.0, + minimum_p, natural_p); +} + +static void +emeus_constraint_layout_get_preferred_height (GtkWidget *widget, + int *minimum_p, + int *natural_p) +{ + emeus_constraint_layout_get_preferred_size (EMEUS_CONSTRAINT_LAYOUT (widget), + GTK_ORIENTATION_VERTICAL, + -1.0, + minimum_p, natural_p); +} + +static void +emeus_constraint_layout_get_preferred_width_for_height (GtkWidget *widget, + int height, + int *minimum_p, + int *natural_p) +{ + emeus_constraint_layout_get_preferred_size (EMEUS_CONSTRAINT_LAYOUT (widget), + GTK_ORIENTATION_HORIZONTAL, + height, + minimum_p, natural_p); +} + +static void +emeus_constraint_layout_get_preferred_height_for_width (GtkWidget *widget, + int width, + int *minimum_p, + int *natural_p) +{ + emeus_constraint_layout_get_preferred_size (EMEUS_CONSTRAINT_LAYOUT (widget), + GTK_ORIENTATION_VERTICAL, + width, + minimum_p, natural_p); +} + +static void +emeus_constraint_layout_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + EmeusConstraintLayout *self = EMEUS_CONSTRAINT_LAYOUT (widget); + Constraint *stay_x, *stay_y; + Constraint *stay_w, *stay_h; + + gtk_widget_set_allocation (widget, allocation); + + if (g_sequence_is_empty (self->children)) + return; + + Variable *layout_top = get_layout_attribute (self, EMEUS_CONSTRAINT_ATTRIBUTE_TOP); + Variable *layout_left = get_layout_attribute (self, EMEUS_CONSTRAINT_ATTRIBUTE_LEFT); + Variable *layout_width = get_layout_attribute (self, EMEUS_CONSTRAINT_ATTRIBUTE_WIDTH); + Variable *layout_height = get_layout_attribute (self, EMEUS_CONSTRAINT_ATTRIBUTE_HEIGHT); + + variable_set_value (layout_left, allocation->x); + stay_x = simplex_solver_add_stay_variable (&self->solver, layout_left, STRENGTH_REQUIRED); + + variable_set_value (layout_top, allocation->y); + stay_y = simplex_solver_add_stay_variable (&self->solver, layout_top, STRENGTH_REQUIRED); + + variable_set_value (layout_width, allocation->width); + stay_w = simplex_solver_add_stay_variable (&self->solver, layout_width, STRENGTH_REQUIRED); + + variable_set_value (layout_height, allocation->height); + stay_h = simplex_solver_add_stay_variable (&self->solver, layout_height, STRENGTH_REQUIRED); + +#ifdef EMEUS_ENABLE_DEBUG + DEBUG (g_debug ("layout [%p] = { .top:%g, .left:%g, .width:%g, .height:%g }", + self, + variable_get_value (layout_top), + variable_get_value (layout_left), + variable_get_value (layout_width), + variable_get_value (layout_height))); +#endif + + EmeusConstraintLayoutChild *child = NULL; + GSequenceIter *iter = g_sequence_get_begin_iter (self->children); + + while (!g_sequence_iter_is_end (iter)) + { + Variable *top, *left, *width, *height; + Variable *center_x, *center_y; + Variable *baseline; + GtkAllocation child_alloc; + GtkRequisition minimum; + + child = g_sequence_get (iter); + iter = g_sequence_iter_next (iter); + + top = get_child_attribute (child, EMEUS_CONSTRAINT_ATTRIBUTE_TOP); + left = get_child_attribute (child, EMEUS_CONSTRAINT_ATTRIBUTE_LEFT); + width = get_child_attribute (child, EMEUS_CONSTRAINT_ATTRIBUTE_WIDTH); + height = get_child_attribute (child, EMEUS_CONSTRAINT_ATTRIBUTE_HEIGHT); + center_x = get_child_attribute (child, EMEUS_CONSTRAINT_ATTRIBUTE_CENTER_X); + center_y = get_child_attribute (child, EMEUS_CONSTRAINT_ATTRIBUTE_CENTER_Y); + baseline = get_child_attribute (child, EMEUS_CONSTRAINT_ATTRIBUTE_BASELINE); + +#ifdef EMEUS_ENABLE_DEBUG + DEBUG (g_debug ("child '%s' [%p] = { " + ".top:%g, .left:%g, .width:%g, .height:%g, " + ".center:(%g, %g), .baseline:%g " + "}", + child->name != NULL ? child->name : "", + child, + variable_get_value (top), + variable_get_value (left), + variable_get_value (width), + variable_get_value (height), + variable_get_value (center_x), variable_get_value (center_y), + variable_get_value (baseline))); +#endif + + gtk_widget_get_preferred_size (GTK_WIDGET (child), &minimum, NULL); + + child_alloc.x = floor (variable_get_value (left)); + child_alloc.y = floor (variable_get_value (top)); + child_alloc.width = variable_get_value (width) > minimum.width + ? ceil (variable_get_value (width)) + : minimum.width; + child_alloc.height = variable_get_value (height) > minimum.height + ? ceil (variable_get_value (height)) + : minimum.height; + + gtk_widget_size_allocate (GTK_WIDGET (child), &child_alloc); + } + + simplex_solver_remove_constraint (&self->solver, stay_x); + simplex_solver_remove_constraint (&self->solver, stay_y); + simplex_solver_remove_constraint (&self->solver, stay_w); + simplex_solver_remove_constraint (&self->solver, stay_h); +} + +static gboolean +emeus_constraint_layout_draw (GtkWidget *widget, + cairo_t *cr) +{ + GtkStyleContext *context = gtk_widget_get_style_context (widget); + int width = gtk_widget_get_allocated_width (widget); + int height = gtk_widget_get_allocated_height (widget); + + gtk_render_background (context, cr, 0, 0, width, height); + gtk_render_frame (context, cr, 0, 0, width, height); + + return GTK_WIDGET_CLASS (emeus_constraint_layout_parent_class)->draw (widget, cr); +} +#endif + +// TODO cjc +static void +emeus_constraint_layout_add (EmeusConstraintLayout *container, + GshoesEle *widget) +{ + EmeusConstraintLayout *self = EMEUS_CONSTRAINT_LAYOUT (container); + + emeus_constraint_layout_pack (self, widget, NULL, NULL); +} + +// TODO cjc +static void +remove_constraints_from_widget (GHashTable *constraints, + GshoesEle *widget) +{ + GHashTableIter iter; + gpointer key; + + if (constraints == NULL || widget == NULL) + return; + + g_hash_table_iter_init (&iter, constraints); + while (g_hash_table_iter_next (&iter, &key, NULL)) + { + EmeusConstraint *constraint = key; + GshoesEle *real_target = constraint->target_object; + + if (real_target == NULL) + continue; +#if 0 // cjc + if (EMEUS_IS_CONSTRAINT_LAYOUT_CHILD (real_target)) + real_target = gtk_bin_get_child (GTK_BIN (real_target)); +#endif + if (constraint->source_object == widget || real_target == widget) + { + emeus_constraint_detach (constraint); + g_hash_table_iter_remove (&iter); + } + } +} + +// TODO cjc +static void +emeus_constraint_layout_remove (EmeusConstraintLayout *container, + GshoesEle *widget) +{ + EmeusConstraintLayout *self = EMEUS_CONSTRAINT_LAYOUT (container); + EmeusConstraintLayoutChild *layout_child; + GshoesEle *child; + GSequenceIter *iter; +#if 0 + if (EMEUS_IS_CONSTRAINT_LAYOUT_CHILD (widget)) + { + layout_child = EMEUS_CONSTRAINT_LAYOUT_CHILD (widget); + child = gtk_bin_get_child (GTK_BIN (widget)); + } + else + { + GtkWidget *parent = gtk_widget_get_parent (widget); + + if (EMEUS_IS_CONSTRAINT_LAYOUT_CHILD (parent)) + { + layout_child = EMEUS_CONSTRAINT_LAYOUT_CHILD (parent); + child = widget; + } + else + { + g_critical ("Attempting to remove widget '%s' but " + "it's not a child of the layout", + G_OBJECT_TYPE_NAME (widget)); + return; + } + } +#else + child = widget; +#endif + if (g_sequence_iter_get_sequence (layout_child->iter) != self->children) + { + g_critical ("Tried to remove non child %p", layout_child); + return; + } + + /* Remove layout constraints */ + remove_constraints_from_widget (self->constraints, child); + + /* Remove other children constraints */ + iter = g_sequence_get_begin_iter (self->children); + while (!g_sequence_iter_is_end (iter)) + { + EmeusConstraintLayoutChild *other = g_sequence_get (iter); + remove_constraints_from_widget (other->constraints, child); + iter = g_sequence_iter_next (iter); + } +#if 0 + gboolean was_visible = gtk_widget_get_visible (GTK_WIDGET (layout_child)); + + gtk_widget_unparent (GTK_WIDGET (layout_child)); +#endif + g_sequence_remove (layout_child->iter); +#if 0 + if (was_visible && gtk_widget_get_visible (GTK_WIDGET (container))) + gtk_widget_queue_resize (GTK_WIDGET (container)); +#endif +} + +#if 0 +// TODO cjc +static void +emeus_constraint_layout_forall (GtkContainer *container, + gboolean internals, + GtkCallback callback, + gpointer data) +{ + EmeusConstraintLayout *self = EMEUS_CONSTRAINT_LAYOUT (container); + + if (g_sequence_is_empty (self->children)) + return; + + GtkWidget *child; + GSequenceIter *iter = g_sequence_get_begin_iter (self->children); + + while (!g_sequence_iter_is_end (iter)) + { + child = g_sequence_get (iter); + iter = g_sequence_iter_next (iter); + + callback (child, data); + } +} +#endif +static GType +emeus_constraint_layout_child_type (EmeusConstraintLayout *container) +{ + return EMEUS_TYPE_CONSTRAINT_LAYOUT_CHILD; +} + +#define TAG_CONSTRAINTS "constraints" +#define TAG_CONSTRAINT "constraint" + +#define ATTR_SOURCE_OBJECT "source-object" +#define ATTR_SOURCE_ATTR "source-attr" +#define ATTR_TARGET_OBJECT "target-object" +#define ATTR_TARGET_ATTR "target-attr" +#define ATTR_RELATION "relation" +#define ATTR_CONSTANT "constant" +#define ATTR_MULTIPLIER "multiplier" +#define ATTR_STRENGTH "strength" + +typedef struct { + char *source_name; + char *source_attr; + char *target_name; + char *target_attr; + char *relation; + char *strength; + double constant; + double multiplier; +} ConstraintData; +#if 0 + +typedef struct { + GObject *object; + GtkBuilder *builder; + GSList *items; +} SubParserData; + +static void +constraint_data_free (gpointer _data) +{ + ConstraintData *data = _data; + + if (data == NULL) + return; + + 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_slice_free (ConstraintData, data); +} + +/* Taken from gtk+/gtk/gtkbuilder.c */ +static gboolean +_gtk_builder_enum_from_string (GType type, + const gchar *string, + gint *enum_value, + GError **error) +{ + GEnumClass *eclass; + GEnumValue *ev; + gchar *endptr; + gint value; + gboolean ret; + + g_return_val_if_fail (G_TYPE_IS_ENUM (type), FALSE); + g_return_val_if_fail (string != NULL, FALSE); + + ret = TRUE; + + endptr = NULL; + errno = 0; + value = g_ascii_strtoull (string, &endptr, 0); + if (errno == 0 && endptr != string) /* parsed a number */ + *enum_value = value; + else + { + eclass = g_type_class_ref (type); + ev = g_enum_get_value_by_name (eclass, string); + if (!ev) + ev = g_enum_get_value_by_nick (eclass, string); + + if (ev) + *enum_value = ev->value; + else + { + g_set_error (error, + GTK_BUILDER_ERROR, + GTK_BUILDER_ERROR_INVALID_VALUE, + "Could not parse enum: '%s'", + string); + ret = FALSE; + } + + g_type_class_unref (eclass); + } + + return ret; +} + +static bool +parse_double (const char *string, + double *value_p, + double default_value) +{ + double value; + char *endptr; + + if (string == NULL) + { + *value_p = default_value; + return false; + } + + errno = 0; + value = g_ascii_strtod (string, &endptr); + if (errno == 0 && endptr != string) + { + *value_p = value; + return true; + } + + *value_p = default_value; + + return false; +} + +static EmeusConstraint * +constraint_data_to_constraint (const ConstraintData *data, + GtkBuilder *builder, + 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-object' must also not " + "have a 'source-attr' attribute"); + return NULL; + } + + source = NULL; + } + else + source = gtk_builder_get_object (builder, data->source_name); + + if (g_strcmp0 (data->target_name, "super") == 0) + target = NULL; + 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 (EMEUS_TYPE_CONSTRAINT_ATTRIBUTE, + data->source_attr, + &source_attr, + error); + if (!res) + return NULL; + } + else + source_attr = EMEUS_CONSTRAINT_ATTRIBUTE_INVALID; + + res = _gtk_builder_enum_from_string (EMEUS_TYPE_CONSTRAINT_ATTRIBUTE, + data->target_attr, + &target_attr, + error); + if (!res) + return NULL; + + if (data->relation != NULL) + { + res = _gtk_builder_enum_from_string (EMEUS_TYPE_CONSTRAINT_RELATION, + data->relation, + &relation, + error); + if (!res) + return NULL; + } + else + relation = EMEUS_CONSTRAINT_RELATION_EQ; + + if (data->strength != NULL) + { + res = _gtk_builder_enum_from_string (EMEUS_TYPE_CONSTRAINT_STRENGTH, + data->strength, + &strength, + error); + } + else + strength = EMEUS_CONSTRAINT_STRENGTH_REQUIRED; + + return emeus_constraint_new (target, target_attr, + relation, + source, source_attr, + data->multiplier, + data->constant, + strength); +} + +static void +constraints_free (gpointer data) +{ + g_slist_free_full (data, constraint_data_free); +} + +static void +constraint_layout_start_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **names, + const gchar **values, + gpointer user_data, + GError **error) +{ + SubParserData *data = user_data; + + if (strcmp (element_name, TAG_CONSTRAINT) == 0) + { + const char *source_name, *source_attr; + const char *target_name, *target_attr; + const char *relation, *strength; + const char *multiplier, *constant; + ConstraintData *cdata; + + if (!g_markup_collect_attributes (element_name, names, values, error, + G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, ATTR_SOURCE_OBJECT, &source_name, + G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, ATTR_SOURCE_ATTR, &source_attr, + G_MARKUP_COLLECT_STRING, ATTR_TARGET_OBJECT, &target_name, + G_MARKUP_COLLECT_STRING, ATTR_TARGET_ATTR, &target_attr, + G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, ATTR_RELATION, &relation, + G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, ATTR_STRENGTH, &strength, + G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, ATTR_MULTIPLIER, &multiplier, + G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, ATTR_CONSTANT, &constant, + G_MARKUP_COLLECT_INVALID)) + { + return; + } + + cdata = g_slice_new (ConstraintData); + cdata->source_name = g_strdup (source_name); + cdata->source_attr = g_strdup (source_attr); + cdata->target_name = g_strdup (target_name); + cdata->target_attr = g_strdup (target_attr); + cdata->relation = g_strdup (relation); + cdata->strength = g_strdup (strength); + parse_double (multiplier, &cdata->multiplier, 1.0); + parse_double (constant, &cdata->constant, 0.0); + + data->items = g_slist_prepend (data->items, cdata); + } +} + +static const GMarkupParser constraint_layout_parser = { + constraint_layout_start_element +}; + +static gboolean +emeus_constraint_layout_buildable_custom_tag_start (GtkBuildable *buildable, + GtkBuilder *builder, + GObject *child, + const gchar *tagname, + GMarkupParser *parser, + gpointer *parser_data) +{ + if (parent_buildable_iface->custom_tag_start (buildable, builder, child, tagname, parser, parser_data)) + return TRUE; + + if (strcmp (tagname, TAG_CONSTRAINTS) == 0) + { + SubParserData *data; + + data = g_slice_new0 (SubParserData); + data->items = NULL; + data->object = G_OBJECT (buildable); + data->builder = builder; + + *parser = constraint_layout_parser; + *parser_data = data; + + return TRUE; + } + + return FALSE; +} + +static void +emeus_constraint_layout_buildable_custom_finished (GtkBuildable *buildable, + GtkBuilder *builder, + GObject *child, + const gchar *tagname, + gpointer user_data) +{ + parent_buildable_iface->custom_finished (buildable, builder, child, tagname, user_data); + + if (strcmp (tagname, TAG_CONSTRAINTS) == 0) + { + SubParserData *data = user_data; + + g_object_set_qdata_full (G_OBJECT (buildable), quark_buildable_constraints, + data->items, + constraints_free); + + g_slice_free (SubParserData, data); + } +} + +static void +emeus_constraint_layout_buildable_parser_finished (GtkBuildable *buildable, + GtkBuilder *builder) +{ + EmeusConstraintLayout *self = EMEUS_CONSTRAINT_LAYOUT (buildable); + GSList *constraints, *l; + GError *error = NULL; + + /* Maintain the order in which the constraints were defined */ + constraints = g_object_get_qdata (G_OBJECT (buildable), quark_buildable_constraints); + constraints = g_slist_reverse (constraints); + + for (l = constraints; l != NULL; l = l->next) + { + const ConstraintData *cdata = l->data; + EmeusConstraint *c = constraint_data_to_constraint (cdata, builder, &error); + + if (error != NULL) + { + g_critical ("Unable to parse constraint '%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_clear_error (&error); + continue; + } + + DEBUG (g_debug ("Adding constraint [%p] from GtkBuilder definition", c)); + emeus_constraint_layout_add_constraint (self, c); + } + + g_object_set_qdata (G_OBJECT (buildable), quark_buildable_constraints, NULL); + + parent_buildable_iface->parser_finished (buildable, builder); +} + +static void +emeus_constraint_layout_buildable_iface_init (GtkBuildableIface *iface) +{ + parent_buildable_iface = g_type_interface_peek_parent (iface); + + iface->parser_finished = emeus_constraint_layout_buildable_parser_finished; + iface->custom_tag_start = emeus_constraint_layout_buildable_custom_tag_start; + iface->custom_finished = emeus_constraint_layout_buildable_custom_finished; +} +#endif +// CJC Gtk 3.22 dependency: +//extern void gtk_widget_class_set_css_name(GtkWidgetClass *widget_class, +// const char *name); +static void +emeus_constraint_layout_class_init (EmeusConstraintLayoutClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + //GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + //GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass); + + //quark_buildable_constraints = g_quark_from_static_string ("-EmeusConstraintLayout-constraints"); + + gobject_class->finalize = emeus_constraint_layout_finalize; + + //widget_class->get_preferred_width = emeus_constraint_layout_get_preferred_width; + //widget_class->get_preferred_height = emeus_constraint_layout_get_preferred_height; + //widget_class->get_preferred_width_for_height = emeus_constraint_layout_get_preferred_width_for_height; + //widget_class->get_preferred_height_for_width = emeus_constraint_layout_get_preferred_height_for_width; + //widget_class->size_allocate = emeus_constraint_layout_size_allocate; + //widget_class->draw = emeus_constraint_layout_draw; + //widget_class->destroy = emeus_constraint_layout_destroy; + + //container_class->add = emeus_constraint_layout_add; + //container_class->remove = emeus_constraint_layout_remove; + //container_class->forall = emeus_constraint_layout_forall; + //container_class->child_type = emeus_constraint_layout_child_type; + //gtk_container_class_handle_border_width (container_class); + + //gtk_widget_class_set_css_name (widget_class, "constraintlayout"); +} + +static void +emeus_constraint_layout_init (EmeusConstraintLayout *self) +{ + //gtk_widget_set_has_window (GTK_WIDGET (self), FALSE); + + simplex_solver_init (&self->solver); + + self->children = g_sequence_new (NULL); + + self->bound_attributes = g_hash_table_new_full (NULL, NULL, + NULL, + (GDestroyNotify) variable_unref); + + self->constraints = g_hash_table_new_full (NULL, NULL, + g_object_unref, + NULL); + + add_layout_stays (self); +} + +/** + * emeus_constraint_layout_new: + * + * Creates a new constraint-based layout manager. + * + * Returns: (transfer full): the newly created layout widget + * + * Since: 1.0 + */ +//GtkWidget * +EmeusConstraintLayout * +emeus_constraint_layout_new (void) +{ + return g_object_new (EMEUS_TYPE_CONSTRAINT_LAYOUT, NULL); +} + +static void +create_layout_constraint (EmeusConstraintLayout *layout, + EmeusConstraint *constraint) +{ + Variable *attr1, *attr2; + Expression *expr; + + attr1 = get_layout_attribute (layout, constraint->target_attribute); + if (constraint->source_attribute == EMEUS_CONSTRAINT_ATTRIBUTE_INVALID) + { + attr2 = simplex_solver_create_variable (constraint->solver, "const", + emeus_constraint_get_constant (constraint)); + + simplex_solver_add_stay_variable (constraint->solver, attr2, STRENGTH_REQUIRED); + + expr = expression_new_from_variable (attr2); + + constraint->constraint = + simplex_solver_add_constraint (constraint->solver, + attr1, + relation_to_operator (constraint->relation), + expr, + strength_to_value (constraint->strength)); + expression_unref (expr); + variable_unref (attr2); + + return; + } + + if (constraint->source_object != NULL) + { + EmeusConstraintLayoutChild *source_child; + + if (EMEUS_IS_CONSTRAINT_LAYOUT_CHILD (constraint->source_object)) + source_child = constraint->source_object; +#if 0 + else + source_child = (EmeusConstraintLayoutChild *) gtk_widget_get_parent (constraint->source_object); +#endif + attr2 = get_child_attribute (source_child, constraint->source_attribute); + } + else + { + attr2 = get_layout_attribute (layout, constraint->source_attribute); + } + + expr = + expression_plus (expression_times (expression_new_from_variable (attr2), + constraint->multiplier), + constraint->constant); + + constraint->constraint = + simplex_solver_add_constraint (constraint->solver, + attr1, + relation_to_operator (constraint->relation), + expr, + strength_to_value (constraint->strength)); + + expression_unref (expr); +} + +static void +add_layout_constraint (EmeusConstraintLayout *layout, + EmeusConstraint *constraint) +{ + if (!emeus_constraint_attach (constraint, layout)) + return; + + g_hash_table_add (layout->constraints, g_object_ref_sink (constraint)); + + if (constraint->is_active) + create_layout_constraint (layout, constraint); +} + +static void +create_child_constraint (EmeusConstraintLayout *layout, + EmeusConstraintLayoutChild *child, + EmeusConstraint *constraint) +{ + Variable *attr1, *attr2; + Expression *expr; + + /* attr1 is the LHS of the linear equation */ + attr1 = get_child_attribute (child, constraint->target_attribute); + + /* attr2 is the RHS of the linear equation; if it's a constant value + * we create a stay constraint for it. Stay constraints ensure that a + * variable won't be modified by the solver. + */ + if (constraint->source_attribute == EMEUS_CONSTRAINT_ATTRIBUTE_INVALID) + { + attr2 = simplex_solver_create_variable (constraint->solver, "const", + emeus_constraint_get_constant (constraint)); + + simplex_solver_add_stay_variable (child->solver, attr2, STRENGTH_REQUIRED); + + expr = expression_new_from_variable (attr2); + + constraint->constraint = + simplex_solver_add_constraint (constraint->solver, + attr1, + relation_to_operator (constraint->relation), + expr, + strength_to_value (constraint->strength)); + + expression_unref (expr); + variable_unref (attr2); + + return; + } + + /* Alternatively, if it's not a constant value, we find the variable + * associated with it + */ + if (constraint->source_object == NULL || constraint->source_object == layout) + { + attr2 = get_layout_attribute (layout, constraint->source_attribute); + } + else + { + EmeusConstraintLayoutChild *source_child; + + if (EMEUS_IS_CONSTRAINT_LAYOUT_CHILD (constraint->source_object)) + source_child = constraint->source_object; +#if 0 //cjc + else + source_child = (EmeusConstraintLayoutChild *) gtk_widget_get_parent (constraint->source_object); + + if (gtk_widget_get_parent ((GtkWidget *) source_child) != (GtkWidget *) layout) + { + g_critical ("Source object %p of type %s does not belong to EmeusConstraintLayout %p", + constraint->source_object, + G_OBJECT_TYPE_NAME (constraint->source_object), + layout); + return; + } +#endif + + attr2 = get_child_attribute (source_child, constraint->source_attribute); + } + + /* Turn attr2 into an expression in the form: + * + * expr = attr2 * multiplier + constant + */ + expr = + expression_plus (expression_times (expression_new_from_variable (attr2), + constraint->multiplier), + constraint->constant); + + constraint->constraint = + simplex_solver_add_constraint (constraint->solver, + attr1, + relation_to_operator (constraint->relation), + expr, + strength_to_value (constraint->strength)); + + expression_unref (expr); +} + +static void +add_child_constraint (EmeusConstraintLayout *layout, + EmeusConstraintLayoutChild *child, + EmeusConstraint *constraint) +{ + if (emeus_constraint_is_attached (constraint)) + { + const char *constraint_description = emeus_constraint_to_string (constraint); + + g_critical ("Constraint '%s' cannot be attached to more than " + "one child.", + constraint_description); + + return; + } + + if (!emeus_constraint_attach (constraint, layout)) + return; + + g_hash_table_add (child->constraints, g_object_ref_sink (constraint)); + + if (constraint->is_active) + create_child_constraint (layout, child, constraint); +} + +static gboolean +remove_child_constraint (EmeusConstraintLayout *layout, + EmeusConstraintLayoutChild *child, + EmeusConstraint *constraint) +{ +#if 0 // cjc + if (constraint->target_object != child) + { + GtkWidget *bin_child = gtk_bin_get_child (GTK_BIN (child)); + + if (constraint->target_object != bin_child) + { + g_critical ("Attempting to remove unknown constraint %p", constraint); + return FALSE; + } + } +#endif + emeus_constraint_detach (constraint); + + g_hash_table_remove (child->constraints, constraint); + + return TRUE; +} + +void +emeus_constraint_layout_activate_constraint (EmeusConstraintLayout *layout, + EmeusConstraint *constraint) +{ + g_assert (constraint->solver == &layout->solver); + + if (constraint->constraint != NULL) + return; + + if (constraint->target_object == NULL) + create_layout_constraint (layout, constraint); + else + { + EmeusConstraintLayoutChild *child = NULL; + + if (EMEUS_IS_CONSTRAINT_LAYOUT_CHILD (constraint->target_object)) + child = constraint->target_object; +#if 0 //cjc + else + { + GtkWidget *parent = gtk_widget_get_parent (constraint->target_object); + + if (parent == NULL || !EMEUS_IS_CONSTRAINT_LAYOUT_CHILD (parent)) + { + g_critical ("Invalid target object for constraint"); + return; + } + + child = EMEUS_CONSTRAINT_LAYOUT_CHILD (parent); + } +#endif + g_assert (child != NULL); + create_child_constraint (layout, child, constraint); + } +} + +void +emeus_constraint_layout_deactivate_constraint (EmeusConstraintLayout *layout, + EmeusConstraint *constraint) +{ + g_assert (constraint->solver == &layout->solver); + + if (constraint->constraint == NULL) + return; + + simplex_solver_remove_constraint (constraint->solver, constraint->constraint); + constraint->constraint = NULL; +} + +SimplexSolver * +emeus_constraint_layout_get_solver (EmeusConstraintLayout *layout) +{ + return &layout->solver; +} + +gboolean +emeus_constraint_layout_has_child_data (EmeusConstraintLayout *layout, + GshoesEle *widget) +{ +#if 0 // TODO cjc + GSequenceIter *iter; + + if (gtk_widget_get_parent (widget) == GTK_WIDGET (layout)) + return TRUE; + + iter = g_sequence_get_begin_iter (layout->children); + while (!g_sequence_iter_is_end (iter)) + { + GtkWidget *child = g_sequence_get (iter); + + iter = g_sequence_iter_next (iter); + + if (gtk_widget_get_parent (widget) == child) + return TRUE; + } +#endif + return FALSE; +} + +static void +layout_add_constraint (EmeusConstraintLayout *layout, + EmeusConstraint *constraint) +{ + EmeusConstraintLayoutChild *child; + GshoesEle *target, *parent; + + if (emeus_constraint_is_attached (constraint)) + { + g_critical ("Constraint '%s' is already attached.", + emeus_constraint_to_string (constraint)); + return; + } + + target = emeus_constraint_get_target_object (constraint); + if (target == NULL) + { + add_layout_constraint (layout, constraint); + return; + } +#if 0 + parent = gtk_widget_get_parent (target); + if (parent == NULL) + { + g_critical ("The target widget '%s' does not have any parent set", + G_OBJECT_TYPE_NAME (target)); + return; + } + + if (!EMEUS_IS_CONSTRAINT_LAYOUT_CHILD (target) && + !EMEUS_IS_CONSTRAINT_LAYOUT_CHILD (parent)) + { + g_critical ("The target widget '%s' is not a direct descendant of " + "the constraint layout", + G_OBJECT_TYPE_NAME (target)); + return; + } +#endif + if (EMEUS_IS_CONSTRAINT_LAYOUT_CHILD (target)) + child = EMEUS_CONSTRAINT_LAYOUT_CHILD (target); + else + child = EMEUS_CONSTRAINT_LAYOUT_CHILD (parent); + + add_child_constraint (layout, child, constraint); +} + +/** + * emeus_constraint_layout_add_constraint: + * @layout: a #EmeusConstraintLayout + * @constraint: a #EmeusConstraint + * + * Adds @constraint to the @layout. + * + * Since: 1.0 + */ +void +emeus_constraint_layout_add_constraint (EmeusConstraintLayout *layout, + EmeusConstraint *constraint) +{ + g_return_if_fail (EMEUS_IS_CONSTRAINT_LAYOUT (layout)); + g_return_if_fail (EMEUS_IS_CONSTRAINT (constraint)); + + layout_add_constraint (layout, constraint); +} + +/** + * emeus_constraint_layout_add_constraints: + * @layout: a #EmeusConstraintLayout + * @first_constraint: the first #EmeusConstraint + * @...: a %NULL-terminated list of #EmeusConstraint instances + * + * Adds multiple #EmeusConstraints at once to the @layout. + * + * See also: emeus_constraint_layout_add_constraints() + * + * Since: 1.0 + */ +void +emeus_constraint_layout_add_constraints (EmeusConstraintLayout *layout, + EmeusConstraint *first_constraint, + ...) +{ + EmeusConstraint *constraint; + va_list args; + + g_return_if_fail (EMEUS_IS_CONSTRAINT_LAYOUT (layout)); + + va_start (args, first_constraint); + + constraint = first_constraint; + while (constraint != NULL) + { + layout_add_constraint (layout, constraint); + + constraint = va_arg (args, EmeusConstraint *); + } + + va_end (args); +} + +/** + * emeus_constraint_layout_pack: + * @layout: a #EmeusConstraintLayout + * @child: a #GtkWidget + * @name: (nullable): an optional name for the @child + * @first_constraint: (nullable): a #EmeusConstraint + * @...: a %NULL-terminated list of #EmeusConstraint instances + * + * Adds @child to the @layout, and applies a list of constraints to it. + * + * This convenience function is the equivalent of calling + * gtk_container_add() and emeus_constraint_layout_child_add_constraint() + * for each constraint instance. + * + * Since: 1.0 + */ +// TODO cjc: may not be used in Shoes with this name +void +emeus_constraint_layout_pack (EmeusConstraintLayout *layout, + GshoesEle *child, + const char *name, + EmeusConstraint *first_constraint, + ...) +{ + EmeusConstraintLayoutChild *layout_child; + EmeusConstraint *constraint; + va_list args; + + g_return_if_fail (EMEUS_IS_CONSTRAINT_LAYOUT (layout)); + g_return_if_fail (GSHOES_IS_ELE (child)); + g_return_if_fail (EMEUS_IS_CONSTRAINT (first_constraint) || first_constraint == NULL); + + //g_return_if_fail (gtk_widget_get_parent (child) == NULL); + + if (EMEUS_IS_CONSTRAINT_LAYOUT_CHILD (child)) + layout_child = EMEUS_CONSTRAINT_LAYOUT_CHILD (child); +#if 0 + else + { + layout_child = (EmeusConstraintLayoutChild *) emeus_constraint_layout_child_new (name); + + gtk_widget_show (GTK_WIDGET (layout_child)); + gtk_container_add (GTK_CONTAINER (layout_child), child); + } +#endif + layout_child->iter = g_sequence_append (layout->children, layout_child); + layout_child->solver = &layout->solver; + g_object_add_weak_pointer (G_OBJECT (layout), (gpointer*) &layout_child->solver); + + //gtk_widget_set_parent (GTK_WIDGET (layout_child), GTK_WIDGET (layout)); + + if (first_constraint == NULL) + return; + + va_start (args, first_constraint); + + constraint = first_constraint; + while (constraint != NULL) + { + add_child_constraint (layout, layout_child, constraint); + + constraint = va_arg (args, EmeusConstraint *); + } + + va_end (args); +} + +/** + * emeus_constraint_layout_get_constraints: + * @layout: a #EmeusConstraintLayout + * + * Retrieves all the constraints used by the @layout. + * + * Returns: (transfer container) (element-type EmeusConstraint): a list of + * #EmeusConstraint instances, owned by the #EmeusConstraintLayout + * + * Since: 1.0 + */ +GList * +emeus_constraint_layout_get_constraints (EmeusConstraintLayout *layout) +{ + EmeusConstraintLayoutChild *child; + GSequenceIter *iter; + GList *res; + + g_return_val_if_fail (EMEUS_IS_CONSTRAINT_LAYOUT (layout), NULL); + + /* First, add all the constraints attached to the layout */ + res = g_hash_table_get_keys (layout->constraints); + + /* Then, iterate over each child, and add the constraints attach to it */ + iter = g_sequence_get_begin_iter (layout->children); + while (!g_sequence_iter_is_end (iter)) + { + child = g_sequence_get (iter); + iter = g_sequence_iter_next (iter); + + res = g_list_concat (res, g_hash_table_get_keys (child->constraints)); + } + + return res; +} + +/** + * emeus_constraint_layout_clear_constraints: + * @layout: a #EmeusConstraintLayout + * + * Removes all constraints from a #EmeusConstraintLayout. + * + * Since: 1.0 + */ +void +emeus_constraint_layout_clear_constraints (EmeusConstraintLayout *layout) +{ + g_return_if_fail (EMEUS_IS_CONSTRAINT_LAYOUT (layout)); + + GHashTableIter iter; + gpointer key; + + g_hash_table_iter_init (&iter, layout->constraints); + while (g_hash_table_iter_next (&iter, &key, NULL)) + { + EmeusConstraint *constraint = key; + + emeus_constraint_detach (constraint); + + g_hash_table_iter_remove (&iter); + } + + g_hash_table_remove_all (layout->bound_attributes); + + GSequenceIter *child_iter = g_sequence_get_begin_iter (layout->children); + while (!g_sequence_iter_is_end (child_iter)) + { + EmeusConstraintLayoutChild *child = g_sequence_get (child_iter); + + child_iter = g_sequence_iter_next (child_iter); + + emeus_constraint_layout_child_clear_constraints (child); + } +} + +static void +emeus_constraint_layout_child_finalize (GObject *gobject) +{ + EmeusConstraintLayoutChild *self = EMEUS_CONSTRAINT_LAYOUT_CHILD (gobject); + + if (self->solver) + { + simplex_solver_freeze (self->solver); + + if (self->width_constraint != NULL) + simplex_solver_remove_constraint (self->solver, self->width_constraint); + + if (self->height_constraint != NULL) + simplex_solver_remove_constraint (self->solver, self->height_constraint); + + if (self->right_constraint != NULL) + simplex_solver_remove_constraint (self->solver, self->right_constraint); + + if (self->bottom_constraint != NULL) + simplex_solver_remove_constraint (self->solver, self->bottom_constraint); + + if (self->center_x_constraint != NULL) + simplex_solver_remove_constraint (self->solver, self->center_x_constraint); + + if (self->center_y_constraint != NULL) + simplex_solver_remove_constraint (self->solver, self->center_y_constraint); + + simplex_solver_thaw (self->solver); +#if 0 + GtkWidget *layout; + if ((layout = gtk_widget_get_parent (GTK_WIDGET (gobject)))) + g_object_remove_weak_pointer (G_OBJECT (layout), (gpointer*) &self->solver); +#endif + self->solver = NULL; + } + + g_free (self->name); + + G_OBJECT_CLASS (emeus_constraint_layout_child_parent_class)->finalize (gobject); +} + +static void +emeus_constraint_layout_child_dispose (GObject *gobject) +{ + EmeusConstraintLayoutChild *self = EMEUS_CONSTRAINT_LAYOUT_CHILD (gobject); + + g_clear_pointer (&self->constraints, g_hash_table_unref); + g_clear_pointer (&self->bound_attributes, g_hash_table_unref); + + G_OBJECT_CLASS (emeus_constraint_layout_child_parent_class)->dispose (gobject); +} + +static void +emeus_constraint_layout_child_set_property (GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + EmeusConstraintLayoutChild *self = EMEUS_CONSTRAINT_LAYOUT_CHILD (gobject); + + switch (prop_id) + { + case CHILD_PROP_NAME: + self->name = g_value_dup_string (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + } +} + +static void +emeus_constraint_layout_child_get_property (GObject *gobject, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + EmeusConstraintLayoutChild *self = EMEUS_CONSTRAINT_LAYOUT_CHILD (gobject); + + switch (prop_id) + { + case CHILD_PROP_NAME: + g_value_set_string (value, self->name); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + } +} +#if 0 // TODO cjc - much to do? +static void +emeus_constraint_layout_child_get_preferred_size (EmeusConstraintLayoutChild *self, + GtkOrientation orientation, + int for_size, + int *minimum_p, + int *natural_p) +{ + GtkWidget *child = gtk_bin_get_child (GTK_BIN (self)); + int child_min = 0; + int child_nat = 0; + Variable *attr = NULL; + + if (self->solver == NULL) + return; + + switch (orientation) + { + case GTK_ORIENTATION_HORIZONTAL: + attr = get_child_attribute (self, EMEUS_CONSTRAINT_ATTRIBUTE_WIDTH); + if (child != NULL && gtk_widget_get_visible (child)) + { + + if (for_size == -1.0) + gtk_widget_get_preferred_width (child, &child_min, &child_nat); + else + gtk_widget_get_preferred_width_for_height (child, for_size, &child_min, &child_nat); + + /* Update the constraint because the min width can change */ + if (self->width_constraint != NULL) + simplex_solver_remove_constraint (self->solver, self->width_constraint); + + Expression *e = expression_new_from_constant (child_min); + + self->width_constraint = + simplex_solver_add_constraint (self->solver, + attr, OPERATOR_TYPE_GE, e, + STRENGTH_MEDIUM); + + expression_unref (e); + } + else + { + if (self->width_constraint != NULL) + { + simplex_solver_remove_constraint (self->solver, self->width_constraint); + self->width_constraint = NULL; + } + } + break; + + case GTK_ORIENTATION_VERTICAL: + attr = get_child_attribute (self, EMEUS_CONSTRAINT_ATTRIBUTE_HEIGHT); + if (child != NULL && gtk_widget_get_visible (child)) + { + if (for_size == -1.0) + gtk_widget_get_preferred_height (child, &child_min, &child_nat); + else + gtk_widget_get_preferred_height_for_width (child, for_size, &child_min, &child_nat); + + /* Update the constraint because the min height can change */ + if (self->height_constraint != NULL) + simplex_solver_remove_constraint (self->solver, self->height_constraint); + + Expression *e = expression_new_from_constant (child_min); + + self->height_constraint = + simplex_solver_add_constraint (self->solver, + attr, OPERATOR_TYPE_GE, e, + STRENGTH_MEDIUM); + + expression_unref (e); + } + else + { + if (self->height_constraint != NULL) + { + simplex_solver_remove_constraint (self->solver, self->height_constraint); + self->height_constraint = NULL; + } + } + break; + } + + if (minimum_p != NULL) + *minimum_p = child_min; + + if (natural_p != NULL) + *natural_p = child_nat; +} + +static void +emeus_constraint_layout_child_get_preferred_width (GtkWidget *widget, + int *minimum_p, + int *natural_p) +{ + emeus_constraint_layout_child_get_preferred_size (EMEUS_CONSTRAINT_LAYOUT_CHILD (widget), + GTK_ORIENTATION_HORIZONTAL, + -1.0, + minimum_p, + natural_p); +} + +static void +emeus_constraint_layout_child_get_preferred_height (GtkWidget *widget, + int *minimum_p, + int *natural_p) +{ + emeus_constraint_layout_child_get_preferred_size (EMEUS_CONSTRAINT_LAYOUT_CHILD (widget), + GTK_ORIENTATION_VERTICAL, + -1.0, + minimum_p, + natural_p); +} + +static void +emeus_constraint_layout_child_get_preferred_width_for_height (GtkWidget *widget, + int height, + int *minimum_p, + int *natural_p) +{ + emeus_constraint_layout_child_get_preferred_size (EMEUS_CONSTRAINT_LAYOUT_CHILD (widget), + GTK_ORIENTATION_HORIZONTAL, + height, + minimum_p, + natural_p); +} + +static void +emeus_constraint_layout_child_get_preferred_height_for_width (GtkWidget *widget, + int width, + int *minimum_p, + int *natural_p) +{ + emeus_constraint_layout_child_get_preferred_size (EMEUS_CONSTRAINT_LAYOUT_CHILD (widget), + GTK_ORIENTATION_VERTICAL, + width, + minimum_p, + natural_p); +} + +static gboolean +emeus_constraint_layout_child_draw (GtkWidget *widget, + cairo_t *cr) +{ + GtkStyleContext *context = gtk_widget_get_style_context (widget); + int width = gtk_widget_get_allocated_width (widget); + int height = gtk_widget_get_allocated_height (widget); + + gtk_render_background (context, cr, 0, 0, width, height); + gtk_render_frame (context, cr, 0, 0, width, height); + + return GTK_WIDGET_CLASS (emeus_constraint_layout_child_parent_class)->draw (widget, cr); +} +#endif + +static void +emeus_constraint_layout_child_class_init (EmeusConstraintLayoutChildClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + //GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + //GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass); + + gobject_class->set_property = emeus_constraint_layout_child_set_property; + gobject_class->get_property = emeus_constraint_layout_child_get_property; + gobject_class->dispose = emeus_constraint_layout_child_dispose; + gobject_class->finalize = emeus_constraint_layout_child_finalize; + + //widget_class->get_preferred_width = emeus_constraint_layout_child_get_preferred_width; + //widget_class->get_preferred_height = emeus_constraint_layout_child_get_preferred_height; + //widget_class->get_preferred_width_for_height = emeus_constraint_layout_child_get_preferred_width_for_height; + //widget_class->get_preferred_height_for_width = emeus_constraint_layout_child_get_preferred_height_for_width; + //widget_class->draw = emeus_constraint_layout_child_draw; + + //gtk_container_class_handle_border_width (container_class); + + /** + * EmeusConstraintLayoutChild:name: + * + * The name of the child. + * + * Since: 1.0 + */ + emeus_constraint_layout_child_properties[CHILD_PROP_NAME] = + g_param_spec_string ("name", "Name", "The name of the child", + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, CHILD_N_PROPS, + emeus_constraint_layout_child_properties); + // Gtk 3.22 tie up: + //gtk_widget_class_set_css_name (widget_class, "constraintlayoutchild"); +} + +static void +emeus_constraint_layout_child_init (EmeusConstraintLayoutChild *self) +{ + //gtk_widget_set_redraw_on_allocate (GTK_WIDGET (self), TRUE); + + self->constraints = g_hash_table_new_full (NULL, NULL, + g_object_unref, + NULL); + + self->bound_attributes = g_hash_table_new_full (NULL, NULL, + NULL, + (GDestroyNotify) variable_unref); +} + +/** + * emeus_constraint_layout_child_new: (constructor) + * @name: (nullable): a name for the child + * + * Creates a new #EmeusConstraintLayoutChild widget. + * + * Returns: (transfer full): the newly created #EmeusConstraintLayoutChild widget + * + * Since: 1.0 + */ +EmeusConstraintLayoutChild * +emeus_constraint_layout_child_new (const char *name) +{ + return g_object_new (EMEUS_TYPE_CONSTRAINT_LAYOUT_CHILD, + "name", name, + NULL); +} + +/** + * emeus_constraint_layout_child_add_constraint: + * @child: a #EmeusConstraintLayoutChild + * @constraint: a #EmeusConstraint + * + * Adds the given @constraint to the list of constraints applied to + * the @child of a #EmeusConstraintLayout + * + * The #EmeusConstraintLayoutChild will own the @constraint until the + * @child is removed, or until the @constraint is removed. + * + * Since: 1.0 + */ +// TODO cjc -- Fix the layout variable - Child should have pointer to parent +void +emeus_constraint_layout_child_add_constraint (EmeusConstraintLayoutChild *child, + EmeusConstraint *constraint) +{ + EmeusConstraintLayout *layout; + //GtkWidget *widget; + + g_return_if_fail (EMEUS_IS_CONSTRAINT_LAYOUT_CHILD (child)); + //g_return_if_fail (gtk_widget_get_parent (GTK_WIDGET (child)) != NULL); + g_return_if_fail (EMEUS_IS_CONSTRAINT (constraint)); + + g_return_if_fail (!emeus_constraint_is_attached (constraint)); + + //widget = GTK_WIDGET (child); + //layout = EMEUS_CONSTRAINT_LAYOUT (gtk_widget_get_parent (widget)); + + add_child_constraint (layout, child, constraint); + + //if (gtk_widget_get_visible (widget)) + // gtk_widget_queue_resize (widget); +} + +/** + * emeus_constraint_layout_child_remove_constraint: + * @child: a #EmeusConstraintLayoutChild + * @constraint: a #EmeusConstraint + * + * Removes the given @constraint from the list of constraints applied + * to the @child of a #EmeusConstraintLayout. + * + * Since: 1.0 + */ +void +emeus_constraint_layout_child_remove_constraint (EmeusConstraintLayoutChild *child, + EmeusConstraint *constraint) +{ + EmeusConstraintLayout *layout; + //GtkWidget *widget; + + g_return_if_fail (EMEUS_IS_CONSTRAINT_LAYOUT_CHILD (child)); + g_return_if_fail (EMEUS_IS_CONSTRAINT (constraint)); + //g_return_if_fail (gtk_widget_get_parent (GTK_WIDGET (child)) != NULL); + g_return_if_fail (!emeus_constraint_is_attached (constraint)); + + //widget = GTK_WIDGET (child); + //layout = EMEUS_CONSTRAINT_LAYOUT (gtk_widget_get_parent (widget)); + + if (!remove_child_constraint (layout, child, constraint)) + return; + + //if (gtk_widget_get_visible (widget)) + // gtk_widget_queue_resize (widget); +} + +/** + * emeus_constraint_layout_child_clear_constraints: + * @child: a #EmeusConstraintLayoutChild + * + * Clears all the constraints associated with a child of #EmeusConstraintLayout. + * + * Since: 1.0 + */ +void +emeus_constraint_layout_child_clear_constraints (EmeusConstraintLayoutChild *child) +{ + g_return_if_fail (EMEUS_IS_CONSTRAINT_LAYOUT_CHILD (child)); + + g_hash_table_remove_all (child->constraints); + g_hash_table_remove_all (child->bound_attributes); + + //gtk_widget_queue_resize (GTK_WIDGET (child)); +} + +/** + * emeus_constraint_layout_child_get_top: + * @child: a #EmeusConstraintLayoutChild + * + * Retrieves the value of the %EMEUS_CONSTRAINT_ATTRIBUTE_TOP for the @child. + * + * Returns: the value of the attribute + * + * Since: 1.0 + */ +int +emeus_constraint_layout_child_get_top (EmeusConstraintLayoutChild *child) +{ + Variable *res; + + g_return_val_if_fail (EMEUS_IS_CONSTRAINT_LAYOUT_CHILD (child), 0); + + res = get_child_attribute (child, EMEUS_CONSTRAINT_ATTRIBUTE_TOP); + + return floor (variable_get_value (res)); +} + +/** + * emeus_constraint_layout_child_get_right: + * @child: a #EmeusConstraintLayoutChild + * + * Retrieves the value of the %EMEUS_CONSTRAINT_ATTRIBUTE_RIGHT for the @child. + * + * Returns: the value of the attribute + * + * Since: 1.0 + */ +int +emeus_constraint_layout_child_get_right (EmeusConstraintLayoutChild *child) +{ + Variable *res; + + g_return_val_if_fail (EMEUS_IS_CONSTRAINT_LAYOUT_CHILD (child), 0); + + res = get_child_attribute (child, EMEUS_CONSTRAINT_ATTRIBUTE_RIGHT); + + return ceil (variable_get_value (res)); +} + +/** + * emeus_constraint_layout_child_get_bottom: + * @child: a #EmeusConstraintLayoutChild + * + * Retrieves the value of the %EMEUS_CONSTRAINT_ATTRIBUTE_BOTTOM for the @child. + * + * Returns: the value of the attribute + * + * Since: 1.0 + */ +int +emeus_constraint_layout_child_get_bottom (EmeusConstraintLayoutChild *child) +{ + Variable *res; + + g_return_val_if_fail (EMEUS_IS_CONSTRAINT_LAYOUT_CHILD (child), 0); + + res = get_child_attribute (child, EMEUS_CONSTRAINT_ATTRIBUTE_BOTTOM); + + return ceil (variable_get_value (res)); +} + +/** + * emeus_constraint_layout_child_get_left: + * @child: a #EmeusConstraintLayoutChild + * + * Retrieves the value of the %EMEUS_CONSTRAINT_ATTRIBUTE_LEFT for the @child. + * + * Returns: the value of the attribute + * + * Since: 1.0 + */ +int +emeus_constraint_layout_child_get_left (EmeusConstraintLayoutChild *child) +{ + Variable *res; + + g_return_val_if_fail (EMEUS_IS_CONSTRAINT_LAYOUT_CHILD (child), 0); + + res = get_child_attribute (child, EMEUS_CONSTRAINT_ATTRIBUTE_LEFT); + + return floor (variable_get_value (res)); +} + +/** + * emeus_constraint_layout_child_get_width: + * @child: a #EmeusConstraintLayoutChild + * + * Retrieves the value of the %EMEUS_CONSTRAINT_ATTRIBUTE_WIDTH for the @child. + * + * Returns: the value of the attribute + * + * Since: 1.0 + */ +int +emeus_constraint_layout_child_get_width (EmeusConstraintLayoutChild *child) +{ + Variable *res; + + g_return_val_if_fail (EMEUS_IS_CONSTRAINT_LAYOUT_CHILD (child), 0); + + res = get_child_attribute (child, EMEUS_CONSTRAINT_ATTRIBUTE_WIDTH); + + return ceil (variable_get_value (res)); +} + +/** + * emeus_constraint_layout_child_get_height: + * @child: a #EmeusConstraintLayoutChild + * + * Retrieves the value of the %EMEUS_CONSTRAINT_ATTRIBUTE_HEIGHT for the @child. + * + * Returns: the value of the attribute + * + * Since: 1.0 + */ +int +emeus_constraint_layout_child_get_height (EmeusConstraintLayoutChild *child) +{ + Variable *res; + + g_return_val_if_fail (EMEUS_IS_CONSTRAINT_LAYOUT_CHILD (child), 0); + + res = get_child_attribute (child, EMEUS_CONSTRAINT_ATTRIBUTE_HEIGHT); + + return ceil (variable_get_value (res)); +} + +/** + * emeus_constraint_layout_child_get_center_x: + * @child: a #EmeusConstraintLayoutChild + * + * Retrieves the value of the %EMEUS_CONSTRAINT_ATTRIBUTE_CENTER_X for the @child. + * + * Returns: the value of the attribute + * + * Since: 1.0 + */ +int +emeus_constraint_layout_child_get_center_x (EmeusConstraintLayoutChild *child) +{ + Variable *res; + + g_return_val_if_fail (EMEUS_IS_CONSTRAINT_LAYOUT_CHILD (child), 0); + + res = get_child_attribute (child, EMEUS_CONSTRAINT_ATTRIBUTE_CENTER_X); + + return ceil (variable_get_value (res)); +} + +/** + * emeus_constraint_layout_child_get_center_y: + * @child: a #EmeusConstraintLayoutChild + * + * Retrieves the value of the %EMEUS_CONSTRAINT_ATTRIBUTE_CENTER_Y for the @child. + * + * Returns: the value of the attribute + * + * Since: 1.0 + */ +int +emeus_constraint_layout_child_get_center_y (EmeusConstraintLayoutChild *child) +{ + Variable *res; + + g_return_val_if_fail (EMEUS_IS_CONSTRAINT_LAYOUT_CHILD (child), 0); + + res = get_child_attribute (child, EMEUS_CONSTRAINT_ATTRIBUTE_CENTER_Y); + + return ceil (variable_get_value (res)); +} + +/** + * emeus_constraint_layout_child_set_intrinsic_width: + * @child: a #EmeusConstraintLayoutChild + * @width: the intrinsic width + * + * Creates a new constraint on the width of the @child. + * + * If @width is a negative value, the constraint is removed. + * + * Since: 1.0 + */ +void +emeus_constraint_layout_child_set_intrinsic_width (EmeusConstraintLayoutChild *child, + int width) +{ + Variable *attr; + + g_return_if_fail (EMEUS_IS_CONSTRAINT_LAYOUT_CHILD (child)); + + if (child->solver == NULL) + return; + + if (child->intrinsic_width == width) + return; + + attr = get_child_attribute (child, EMEUS_CONSTRAINT_ATTRIBUTE_WIDTH); + + if (child->intrinsic_width < 0) + { + child->width_constraint = + simplex_solver_add_edit_variable (child->solver, attr, STRENGTH_REQUIRED); + } + + if (width < 0) + { + simplex_solver_remove_constraint (child->solver, child->width_constraint); + child->width_constraint = NULL; + } + + child->intrinsic_width = width; + + if (child->intrinsic_width > 0) + { + simplex_solver_suggest_value (child->solver, attr, child->intrinsic_width); + simplex_solver_resolve (child->solver); + } + + //if (gtk_widget_get_visible (GTK_WIDGET (child))) + // gtk_widget_queue_resize (GTK_WIDGET (child)); +} + +/** + * emeus_constraint_layout_child_set_intrinsic_height: + * @child: a #EmeusConstraintLayoutChild + * @height: the intrinsic width + * + * Creates a new constraint on the height of the @child. + * + * If @height is a negative value, the constraint is removed. + * + * Since: 1.0 + */ +void +emeus_constraint_layout_child_set_intrinsic_height (EmeusConstraintLayoutChild *child, + int height) +{ + Variable *attr; + + g_return_if_fail (EMEUS_IS_CONSTRAINT_LAYOUT_CHILD (child)); + + if (child->solver == NULL) + return; + + if (child->intrinsic_height == height) + return; + + attr = get_child_attribute (child, EMEUS_CONSTRAINT_ATTRIBUTE_HEIGHT); + + if (child->intrinsic_height < 0) + { + child->height_constraint = + simplex_solver_add_edit_variable (child->solver, attr, STRENGTH_REQUIRED - 1); + } + + if (height < 0) + { + simplex_solver_remove_constraint (child->solver, child->height_constraint); + child->height_constraint = NULL; + } + + child->intrinsic_height = height; + + if (child->intrinsic_height > 0) + { + simplex_solver_suggest_value (child->solver, attr, height); + simplex_solver_resolve (child->solver); + } + + //if (gtk_widget_get_visible (GTK_WIDGET (child))) + // gtk_widget_queue_resize (GTK_WIDGET (child)); +} + +/** + * emeus_constraint_layout_child_get_name: + * @child: a #EmeusConstraintLayoutChild + * + * Retrieves the name of the @child. + * + * Returns: (nullable) (transfer none): the name of the child + * + * Since: 1.0 + */ +const char * +emeus_constraint_layout_child_get_name (EmeusConstraintLayoutChild *child) +{ + g_return_val_if_fail (EMEUS_IS_CONSTRAINT_LAYOUT_CHILD (child), NULL); + + return child->name; +} diff --git a/shoes/layout/emeus-constraint-layout.h b/shoes/layout/emeus-constraint-layout.h new file mode 100644 index 00000000..f948998c --- /dev/null +++ b/shoes/layout/emeus-constraint-layout.h @@ -0,0 +1,115 @@ +/* emeus-constraint-layout.h: The constraint layout manager + * + * Copyright 2016 Endless + * + * 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 . + */ + +#pragma once + +//#include +#include "shoes/layout/emeus-types.h" +#include "shoes/layout/emeus-constraint.h" +#include "shoes/layout/gshoes-ele.h" +G_BEGIN_DECLS + +#define EMEUS_TYPE_CONSTRAINT_LAYOUT (emeus_constraint_layout_get_type()) + +/** + * EmeusConstraintLayout: + * + * A container that uses constraints to determine the layout policy. + * + * The contents of the `EmeusConstraintLayout` are private and should never be + * accessed directly. + * + * Since: 1.0 + */ +EMEUS_AVAILABLE_IN_1_0 +G_DECLARE_FINAL_TYPE (EmeusConstraintLayout, emeus_constraint_layout, EMEUS, CONSTRAINT_LAYOUT, GObject) + +EMEUS_AVAILABLE_IN_1_0 +EmeusConstraintLayout * emeus_constraint_layout_new (void); +EMEUS_AVAILABLE_IN_1_0 +void emeus_constraint_layout_pack (EmeusConstraintLayout *layout, + GshoesEle *child, + const char *name, + EmeusConstraint *first_constraint, + ...); +EMEUS_AVAILABLE_IN_1_0 +void emeus_constraint_layout_add_constraint (EmeusConstraintLayout *layout, + EmeusConstraint *constraint); +EMEUS_AVAILABLE_IN_1_0 +void emeus_constraint_layout_add_constraints (EmeusConstraintLayout *layout, + EmeusConstraint *first_constraint, + ...) G_GNUC_NULL_TERMINATED; + +EMEUS_AVAILABLE_IN_1_0 +GList * emeus_constraint_layout_get_constraints (EmeusConstraintLayout *layout); + +EMEUS_AVAILABLE_IN_1_0 +void emeus_constraint_layout_clear_constraints (EmeusConstraintLayout *layout); + +#define EMEUS_TYPE_CONSTRAINT_LAYOUT_CHILD (emeus_constraint_layout_child_get_type()) + +/** + * EmeusConstraintLayoutChild: + * + * A child in a #EmeusConstraintLayout. + * + * The contents of the `EmeusConstraintLayoutChild` structure are private and + * should never be accessed directly. + * + * Since: 1.0 + */ +EMEUS_AVAILABLE_IN_1_0 +G_DECLARE_FINAL_TYPE (EmeusConstraintLayoutChild, emeus_constraint_layout_child, EMEUS, CONSTRAINT_LAYOUT_CHILD, GObject) + +EMEUS_AVAILABLE_IN_1_0 +EmeusConstraintLayoutChild * emeus_constraint_layout_child_new (const char *name); + +EMEUS_AVAILABLE_IN_1_0 +const char * emeus_constraint_layout_child_get_name (EmeusConstraintLayoutChild *child); +EMEUS_AVAILABLE_IN_1_0 +int emeus_constraint_layout_child_get_top (EmeusConstraintLayoutChild *child); +EMEUS_AVAILABLE_IN_1_0 +int emeus_constraint_layout_child_get_right (EmeusConstraintLayoutChild *child); +EMEUS_AVAILABLE_IN_1_0 +int emeus_constraint_layout_child_get_bottom (EmeusConstraintLayoutChild *child); +EMEUS_AVAILABLE_IN_1_0 +int emeus_constraint_layout_child_get_left (EmeusConstraintLayoutChild *child); +EMEUS_AVAILABLE_IN_1_0 +int emeus_constraint_layout_child_get_width (EmeusConstraintLayoutChild *child); +EMEUS_AVAILABLE_IN_1_0 +int emeus_constraint_layout_child_get_height (EmeusConstraintLayoutChild *child); +EMEUS_AVAILABLE_IN_1_0 +int emeus_constraint_layout_child_get_center_x (EmeusConstraintLayoutChild *child); +EMEUS_AVAILABLE_IN_1_0 +int emeus_constraint_layout_child_get_center_y (EmeusConstraintLayoutChild *child); +EMEUS_AVAILABLE_IN_1_0 +void emeus_constraint_layout_child_set_intrinsic_width (EmeusConstraintLayoutChild *child, + int width); +EMEUS_AVAILABLE_IN_1_0 +void emeus_constraint_layout_child_set_intrinsic_height (EmeusConstraintLayoutChild *child, + int height); +EMEUS_AVAILABLE_IN_1_0 +void emeus_constraint_layout_child_add_constraint (EmeusConstraintLayoutChild *child, + EmeusConstraint *constraint); +EMEUS_AVAILABLE_IN_1_0 +void emeus_constraint_layout_child_remove_constraint (EmeusConstraintLayoutChild *child, + EmeusConstraint *constraint); +EMEUS_AVAILABLE_IN_1_0 +void emeus_constraint_layout_child_clear_constraints (EmeusConstraintLayoutChild *child); + +G_END_DECLS diff --git a/shoes/layout/shoes-vfl.c b/shoes/layout/shoes-vfl.c index 26c91e0b..f0c02815 100644 --- a/shoes/layout/shoes-vfl.c +++ b/shoes/layout/shoes-vfl.c @@ -3,6 +3,16 @@ * Emeus has A LOT of Gobject/Glib stuff which we keep. Some of the * emeus code has been deleted (the gtk interface builder for example). * + * The shoes protocol for layouts is 'setup', add_ele..., finish and then + * responding to size events. The emeus-contraint-layout.c uses the much + * more complicated Gtk process. We use the emeus names, macros and code + * even when it's been modified for the Shoes/ruby way. + * + * We use a GObject implementation of EmeusConstraintLayout without all the + * Gtk stuff. Same for EmeusContraintLayoutChild. + * + * We also have a a Ruby class CassowaryConstraint for mapping between + * Ruby and EmeusConstraint (and VflConstraint) */ #include "shoes/app.h" #include "shoes/canvas.h"