| /* Copyright (C) 2019 Red Hat, Inc. |
| * |
| * 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 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library. If not, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| #include <gtk/gtk.h> |
| #include "constraint-view.h" |
| |
| struct _ConstraintView |
| { |
| GtkWidget parent; |
| |
| GListModel *model; |
| |
| GtkWidget *drag_widget; |
| }; |
| |
| G_DEFINE_TYPE (ConstraintView, constraint_view, GTK_TYPE_WIDGET); |
| |
| static void |
| constraint_view_dispose (GObject *object) |
| { |
| ConstraintView *view = CONSTRAINT_VIEW (object); |
| GtkWidget *child; |
| |
| while ((child = gtk_widget_get_first_child (GTK_WIDGET (view))) != NULL) |
| gtk_widget_unparent (child); |
| |
| g_clear_object (&view->model); |
| |
| G_OBJECT_CLASS (constraint_view_parent_class)->dispose (object); |
| } |
| |
| static void |
| constraint_view_class_init (ConstraintViewClass *klass) |
| { |
| GObjectClass *object_class = G_OBJECT_CLASS (klass); |
| GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); |
| |
| object_class->dispose = constraint_view_dispose; |
| |
| gtk_widget_class_set_css_name (widget_class, "constraintview"); |
| } |
| |
| static void |
| update_weak_position (ConstraintView *self, |
| GtkWidget *child, |
| double x, |
| double y) |
| { |
| GtkLayoutManager *manager; |
| GtkConstraint *constraint; |
| |
| manager = gtk_widget_get_layout_manager (GTK_WIDGET (self)); |
| constraint = (GtkConstraint *)g_object_get_data (G_OBJECT (child), "x-constraint"); |
| if (constraint) |
| { |
| gtk_constraint_layout_remove_constraint (GTK_CONSTRAINT_LAYOUT (manager), |
| constraint); |
| g_object_set_data (G_OBJECT (child), "x-constraint", NULL); |
| } |
| if (x != -100) |
| { |
| constraint = gtk_constraint_new_constant (child, |
| GTK_CONSTRAINT_ATTRIBUTE_CENTER_X, |
| GTK_CONSTRAINT_RELATION_EQ, |
| x, |
| GTK_CONSTRAINT_STRENGTH_WEAK); |
| g_object_set_data (G_OBJECT (constraint), "internal", (char *)"yes"); |
| gtk_constraint_layout_add_constraint (GTK_CONSTRAINT_LAYOUT (manager), |
| constraint); |
| g_object_set_data (G_OBJECT (child), "x-constraint", constraint); |
| } |
| |
| constraint = (GtkConstraint *)g_object_get_data (G_OBJECT (child), "y-constraint"); |
| if (constraint) |
| { |
| gtk_constraint_layout_remove_constraint (GTK_CONSTRAINT_LAYOUT (manager), |
| constraint); |
| g_object_set_data (G_OBJECT (child), "y-constraint", NULL); |
| } |
| if (y != -100) |
| { |
| constraint = gtk_constraint_new_constant (child, |
| GTK_CONSTRAINT_ATTRIBUTE_CENTER_Y, |
| GTK_CONSTRAINT_RELATION_EQ, |
| y, |
| GTK_CONSTRAINT_STRENGTH_WEAK); |
| g_object_set_data (G_OBJECT (constraint), "internal", (char *)"yes"); |
| gtk_constraint_layout_add_constraint (GTK_CONSTRAINT_LAYOUT (manager), |
| constraint); |
| g_object_set_data (G_OBJECT (child), "y-constraint", constraint); |
| } |
| } |
| |
| static void |
| drag_begin (GtkGestureDrag *drag, |
| double start_x, |
| double start_y, |
| ConstraintView *self) |
| { |
| GtkWidget *widget; |
| |
| widget = gtk_widget_pick (GTK_WIDGET (self), start_x, start_y, GTK_PICK_DEFAULT); |
| |
| if (GTK_IS_LABEL (widget)) |
| { |
| widget = gtk_widget_get_ancestor (widget, GTK_TYPE_FRAME); |
| if (widget && |
| gtk_widget_get_parent (widget) == (GtkWidget *)self) |
| { |
| self->drag_widget = widget; |
| } |
| } |
| } |
| |
| static void |
| drag_update (GtkGestureDrag *drag, |
| double offset_x, |
| double offset_y, |
| ConstraintView *self) |
| { |
| double x, y; |
| |
| if (!self->drag_widget) |
| return; |
| |
| gtk_gesture_drag_get_start_point (drag, &x, &y); |
| update_weak_position (self, self->drag_widget, x + offset_x, y + offset_y); |
| } |
| |
| static void |
| drag_end (GtkGestureDrag *drag, |
| double offset_x, |
| double offset_y, |
| ConstraintView *self) |
| { |
| self->drag_widget = NULL; |
| } |
| |
| static gboolean |
| omit_internal (gpointer item, gpointer user_data) |
| { |
| if (g_object_get_data (G_OBJECT (item), "internal")) |
| return FALSE; |
| |
| return TRUE; |
| } |
| |
| static void |
| constraint_view_init (ConstraintView *self) |
| { |
| GtkLayoutManager *manager; |
| GtkEventController *controller; |
| GListStore *list; |
| GListModel *all_children; |
| GListModel *all_constraints; |
| GListModel *guides; |
| GListModel *children; |
| GListModel *constraints; |
| GtkFilter *filter; |
| |
| manager = gtk_constraint_layout_new (); |
| gtk_widget_set_layout_manager (GTK_WIDGET (self), manager); |
| |
| guides = gtk_constraint_layout_observe_guides (GTK_CONSTRAINT_LAYOUT (manager)); |
| |
| all_constraints = gtk_constraint_layout_observe_constraints (GTK_CONSTRAINT_LAYOUT (manager)); |
| filter = GTK_FILTER (gtk_custom_filter_new (omit_internal, NULL, NULL)); |
| constraints = (GListModel *)gtk_filter_list_model_new (all_constraints, filter); |
| |
| all_children = gtk_widget_observe_children (GTK_WIDGET (self)); |
| filter = GTK_FILTER (gtk_custom_filter_new (omit_internal, NULL, NULL)); |
| children = (GListModel *)gtk_filter_list_model_new (all_children, filter); |
| |
| list = g_list_store_new (G_TYPE_LIST_MODEL); |
| g_list_store_append (list, children); |
| g_list_store_append (list, guides); |
| g_list_store_append (list, constraints); |
| g_object_unref (children); |
| g_object_unref (guides); |
| g_object_unref (constraints); |
| |
| self->model = G_LIST_MODEL (gtk_flatten_list_model_new (G_LIST_MODEL (list))); |
| |
| controller = (GtkEventController *)gtk_gesture_drag_new (); |
| g_signal_connect (controller, "drag-begin", G_CALLBACK (drag_begin), self); |
| g_signal_connect (controller, "drag-update", G_CALLBACK (drag_update), self); |
| g_signal_connect (controller, "drag-end", G_CALLBACK (drag_end), self); |
| gtk_widget_add_controller (GTK_WIDGET (self), controller); |
| } |
| |
| ConstraintView * |
| constraint_view_new (void) |
| { |
| return g_object_new (CONSTRAINT_VIEW_TYPE, NULL); |
| } |
| |
| void |
| constraint_view_add_child (ConstraintView *view, |
| const char *name) |
| { |
| GtkWidget *frame; |
| GtkWidget *label; |
| |
| label = gtk_label_new (name); |
| frame = gtk_frame_new (NULL); |
| gtk_widget_add_css_class (frame, "child"); |
| gtk_widget_set_name (frame, name); |
| gtk_frame_set_child (GTK_FRAME (frame), label); |
| gtk_widget_set_parent (frame, GTK_WIDGET (view)); |
| |
| update_weak_position (view, frame, 100, 100); |
| } |
| |
| void |
| constraint_view_remove_child (ConstraintView *view, |
| GtkWidget *child) |
| { |
| update_weak_position (view, child, -100, -100); |
| gtk_widget_unparent (child); |
| } |
| |
| void |
| constraint_view_add_guide (ConstraintView *view, |
| GtkConstraintGuide *guide) |
| { |
| GtkConstraintLayout *layout; |
| GtkWidget *frame; |
| GtkWidget *label; |
| const char *name; |
| GtkConstraint *constraint; |
| struct { |
| const char *name; |
| GtkConstraintAttribute attr; |
| } names[] = { |
| { "left-constraint", GTK_CONSTRAINT_ATTRIBUTE_LEFT }, |
| { "top-constraint", GTK_CONSTRAINT_ATTRIBUTE_TOP }, |
| { "width-constraint", GTK_CONSTRAINT_ATTRIBUTE_WIDTH }, |
| { "height-constraint", GTK_CONSTRAINT_ATTRIBUTE_HEIGHT }, |
| }; |
| int i; |
| |
| name = gtk_constraint_guide_get_name (guide); |
| label = gtk_label_new (name); |
| g_object_bind_property (guide, "name", |
| label, "label", |
| G_BINDING_DEFAULT); |
| |
| frame = gtk_frame_new (NULL); |
| gtk_widget_add_css_class (frame, "guide"); |
| g_object_set_data (G_OBJECT (frame), "internal", (char *)"yes"); |
| gtk_frame_set_child (GTK_FRAME (frame), label); |
| gtk_widget_insert_after (frame, GTK_WIDGET (view), NULL); |
| |
| g_object_set_data (G_OBJECT (guide), "frame", frame); |
| |
| layout = GTK_CONSTRAINT_LAYOUT (gtk_widget_get_layout_manager (GTK_WIDGET (view))); |
| gtk_constraint_layout_add_guide (layout, g_object_ref (guide)); |
| |
| for (i = 0; i < G_N_ELEMENTS (names); i++) |
| { |
| constraint = gtk_constraint_new (frame, |
| names[i].attr, |
| GTK_CONSTRAINT_RELATION_EQ, |
| guide, |
| names[i].attr, |
| 1.0, 0.0, |
| GTK_CONSTRAINT_STRENGTH_REQUIRED); |
| g_object_set_data (G_OBJECT (constraint), "internal", (char *)"yes"); |
| gtk_constraint_layout_add_constraint (layout, constraint); |
| g_object_set_data (G_OBJECT (guide), names[i].name, constraint); |
| } |
| |
| update_weak_position (view, frame, 150, 150); |
| } |
| |
| void |
| constraint_view_remove_guide (ConstraintView *view, |
| GtkConstraintGuide *guide) |
| { |
| GtkConstraintLayout *layout; |
| GtkWidget *frame; |
| GtkConstraint *constraint; |
| const char *names[] = { |
| "left-constraint", |
| "top-constraint", |
| "width-constraint", |
| "height-constraint" |
| }; |
| int i; |
| |
| layout = GTK_CONSTRAINT_LAYOUT (gtk_widget_get_layout_manager (GTK_WIDGET (view))); |
| |
| for (i = 0; i < G_N_ELEMENTS (names); i++) |
| { |
| constraint = (GtkConstraint*)g_object_get_data (G_OBJECT (guide), names[i]); |
| gtk_constraint_layout_remove_constraint (layout, constraint); |
| } |
| |
| frame = (GtkWidget *)g_object_get_data (G_OBJECT (guide), "frame"); |
| update_weak_position (view, frame, -100, -100); |
| gtk_widget_unparent (frame); |
| |
| gtk_constraint_layout_remove_guide (layout, guide); |
| } |
| |
| void |
| constraint_view_add_constraint (ConstraintView *view, |
| GtkConstraint *constraint) |
| { |
| GtkLayoutManager *manager; |
| |
| manager = gtk_widget_get_layout_manager (GTK_WIDGET (view)); |
| gtk_constraint_layout_add_constraint (GTK_CONSTRAINT_LAYOUT (manager), |
| g_object_ref (constraint)); |
| } |
| |
| void |
| constraint_view_remove_constraint (ConstraintView *view, |
| GtkConstraint *constraint) |
| { |
| GtkLayoutManager *manager; |
| |
| manager = gtk_widget_get_layout_manager (GTK_WIDGET (view)); |
| gtk_constraint_layout_remove_constraint (GTK_CONSTRAINT_LAYOUT (manager), |
| constraint); |
| } |
| |
| GListModel * |
| constraint_view_get_model (ConstraintView *view) |
| { |
| return view->model; |
| } |