1/* Copyright (C) 2019 Red Hat, Inc.
2 *
3 * This library is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU Lesser General Public
5 * License as published by the Free Software Foundation; either
6 * version 2 of the License, or (at your option) any later version.
7 *
8 * This library is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public
14 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#include <gtk/gtk.h>
18#include "constraint-view.h"
19
20struct _ConstraintView
21{
22 GtkWidget parent;
23
24 GListModel *model;
25
26 GtkWidget *drag_widget;
27};
28
29G_DEFINE_TYPE (ConstraintView, constraint_view, GTK_TYPE_WIDGET);
30
31static void
32constraint_view_dispose (GObject *object)
33{
34 ConstraintView *view = CONSTRAINT_VIEW (ptr: object);
35 GtkWidget *child;
36
37 while ((child = gtk_widget_get_first_child (GTK_WIDGET (view))) != NULL)
38 gtk_widget_unparent (widget: child);
39
40 g_clear_object (&view->model);
41
42 G_OBJECT_CLASS (constraint_view_parent_class)->dispose (object);
43}
44
45static void
46constraint_view_class_init (ConstraintViewClass *klass)
47{
48 GObjectClass *object_class = G_OBJECT_CLASS (klass);
49 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
50
51 object_class->dispose = constraint_view_dispose;
52
53 gtk_widget_class_set_css_name (widget_class, name: "constraintview");
54}
55
56static void
57update_weak_position (ConstraintView *self,
58 GtkWidget *child,
59 double x,
60 double y)
61{
62 GtkLayoutManager *manager;
63 GtkConstraint *constraint;
64
65 manager = gtk_widget_get_layout_manager (GTK_WIDGET (self));
66 constraint = (GtkConstraint *)g_object_get_data (G_OBJECT (child), key: "x-constraint");
67 if (constraint)
68 {
69 gtk_constraint_layout_remove_constraint (layout: GTK_CONSTRAINT_LAYOUT (ptr: manager),
70 constraint);
71 g_object_set_data (G_OBJECT (child), key: "x-constraint", NULL);
72 }
73 if (x != -100)
74 {
75 constraint = gtk_constraint_new_constant (target: child,
76 target_attribute: GTK_CONSTRAINT_ATTRIBUTE_CENTER_X,
77 relation: GTK_CONSTRAINT_RELATION_EQ,
78 constant: x,
79 strength: GTK_CONSTRAINT_STRENGTH_WEAK);
80 g_object_set_data (G_OBJECT (constraint), key: "internal", data: (char *)"yes");
81 gtk_constraint_layout_add_constraint (layout: GTK_CONSTRAINT_LAYOUT (ptr: manager),
82 constraint);
83 g_object_set_data (G_OBJECT (child), key: "x-constraint", data: constraint);
84 }
85
86 constraint = (GtkConstraint *)g_object_get_data (G_OBJECT (child), key: "y-constraint");
87 if (constraint)
88 {
89 gtk_constraint_layout_remove_constraint (layout: GTK_CONSTRAINT_LAYOUT (ptr: manager),
90 constraint);
91 g_object_set_data (G_OBJECT (child), key: "y-constraint", NULL);
92 }
93 if (y != -100)
94 {
95 constraint = gtk_constraint_new_constant (target: child,
96 target_attribute: GTK_CONSTRAINT_ATTRIBUTE_CENTER_Y,
97 relation: GTK_CONSTRAINT_RELATION_EQ,
98 constant: y,
99 strength: GTK_CONSTRAINT_STRENGTH_WEAK);
100 g_object_set_data (G_OBJECT (constraint), key: "internal", data: (char *)"yes");
101 gtk_constraint_layout_add_constraint (layout: GTK_CONSTRAINT_LAYOUT (ptr: manager),
102 constraint);
103 g_object_set_data (G_OBJECT (child), key: "y-constraint", data: constraint);
104 }
105}
106
107static void
108drag_begin (GtkGestureDrag *drag,
109 double start_x,
110 double start_y,
111 ConstraintView *self)
112{
113 GtkWidget *widget;
114
115 widget = gtk_widget_pick (GTK_WIDGET (self), x: start_x, y: start_y, flags: GTK_PICK_DEFAULT);
116
117 if (GTK_IS_LABEL (widget))
118 {
119 widget = gtk_widget_get_ancestor (widget, GTK_TYPE_FRAME);
120 if (widget &&
121 gtk_widget_get_parent (widget) == (GtkWidget *)self)
122 {
123 self->drag_widget = widget;
124 }
125 }
126}
127
128static void
129drag_update (GtkGestureDrag *drag,
130 double offset_x,
131 double offset_y,
132 ConstraintView *self)
133{
134 double x, y;
135
136 if (!self->drag_widget)
137 return;
138
139 gtk_gesture_drag_get_start_point (gesture: drag, x: &x, y: &y);
140 update_weak_position (self, child: self->drag_widget, x: x + offset_x, y: y + offset_y);
141}
142
143static void
144drag_end (GtkGestureDrag *drag,
145 double offset_x,
146 double offset_y,
147 ConstraintView *self)
148{
149 self->drag_widget = NULL;
150}
151
152static gboolean
153omit_internal (gpointer item, gpointer user_data)
154{
155 if (g_object_get_data (G_OBJECT (item), key: "internal"))
156 return FALSE;
157
158 return TRUE;
159}
160
161static void
162constraint_view_init (ConstraintView *self)
163{
164 GtkLayoutManager *manager;
165 GtkEventController *controller;
166 GListStore *list;
167 GListModel *all_children;
168 GListModel *all_constraints;
169 GListModel *guides;
170 GListModel *children;
171 GListModel *constraints;
172 GtkFilter *filter;
173
174 manager = gtk_constraint_layout_new ();
175 gtk_widget_set_layout_manager (GTK_WIDGET (self), layout_manager: manager);
176
177 guides = gtk_constraint_layout_observe_guides (layout: GTK_CONSTRAINT_LAYOUT (ptr: manager));
178
179 all_constraints = gtk_constraint_layout_observe_constraints (layout: GTK_CONSTRAINT_LAYOUT (ptr: manager));
180 filter = GTK_FILTER (ptr: gtk_custom_filter_new (match_func: omit_internal, NULL, NULL));
181 constraints = (GListModel *)gtk_filter_list_model_new (model: all_constraints, filter);
182
183 all_children = gtk_widget_observe_children (GTK_WIDGET (self));
184 filter = GTK_FILTER (ptr: gtk_custom_filter_new (match_func: omit_internal, NULL, NULL));
185 children = (GListModel *)gtk_filter_list_model_new (model: all_children, filter);
186
187 list = g_list_store_new (G_TYPE_LIST_MODEL);
188 g_list_store_append (store: list, item: children);
189 g_list_store_append (store: list, item: guides);
190 g_list_store_append (store: list, item: constraints);
191 g_object_unref (object: children);
192 g_object_unref (object: guides);
193 g_object_unref (object: constraints);
194
195 self->model = G_LIST_MODEL (ptr: gtk_flatten_list_model_new (model: G_LIST_MODEL (ptr: list)));
196
197 controller = (GtkEventController *)gtk_gesture_drag_new ();
198 g_signal_connect (controller, "drag-begin", G_CALLBACK (drag_begin), self);
199 g_signal_connect (controller, "drag-update", G_CALLBACK (drag_update), self);
200 g_signal_connect (controller, "drag-end", G_CALLBACK (drag_end), self);
201 gtk_widget_add_controller (GTK_WIDGET (self), controller);
202}
203
204ConstraintView *
205constraint_view_new (void)
206{
207 return g_object_new (CONSTRAINT_VIEW_TYPE, NULL);
208}
209
210void
211constraint_view_add_child (ConstraintView *view,
212 const char *name)
213{
214 GtkWidget *frame;
215 GtkWidget *label;
216
217 label = gtk_label_new (str: name);
218 frame = gtk_frame_new (NULL);
219 gtk_widget_add_css_class (widget: frame, css_class: "child");
220 gtk_widget_set_name (widget: frame, name);
221 gtk_frame_set_child (GTK_FRAME (frame), child: label);
222 gtk_widget_set_parent (widget: frame, GTK_WIDGET (view));
223
224 update_weak_position (self: view, child: frame, x: 100, y: 100);
225}
226
227void
228constraint_view_remove_child (ConstraintView *view,
229 GtkWidget *child)
230{
231 update_weak_position (self: view, child, x: -100, y: -100);
232 gtk_widget_unparent (widget: child);
233}
234
235void
236constraint_view_add_guide (ConstraintView *view,
237 GtkConstraintGuide *guide)
238{
239 GtkConstraintLayout *layout;
240 GtkWidget *frame;
241 GtkWidget *label;
242 const char *name;
243 GtkConstraint *constraint;
244 struct {
245 const char *name;
246 GtkConstraintAttribute attr;
247 } names[] = {
248 { "left-constraint", GTK_CONSTRAINT_ATTRIBUTE_LEFT },
249 { "top-constraint", GTK_CONSTRAINT_ATTRIBUTE_TOP },
250 { "width-constraint", GTK_CONSTRAINT_ATTRIBUTE_WIDTH },
251 { "height-constraint", GTK_CONSTRAINT_ATTRIBUTE_HEIGHT },
252 };
253 int i;
254
255 name = gtk_constraint_guide_get_name (guide);
256 label = gtk_label_new (str: name);
257 g_object_bind_property (source: guide, source_property: "name",
258 target: label, target_property: "label",
259 flags: G_BINDING_DEFAULT);
260
261 frame = gtk_frame_new (NULL);
262 gtk_widget_add_css_class (widget: frame, css_class: "guide");
263 g_object_set_data (G_OBJECT (frame), key: "internal", data: (char *)"yes");
264 gtk_frame_set_child (GTK_FRAME (frame), child: label);
265 gtk_widget_insert_after (widget: frame, GTK_WIDGET (view), NULL);
266
267 g_object_set_data (G_OBJECT (guide), key: "frame", data: frame);
268
269 layout = GTK_CONSTRAINT_LAYOUT (ptr: gtk_widget_get_layout_manager (GTK_WIDGET (view)));
270 gtk_constraint_layout_add_guide (layout, g_object_ref (guide));
271
272 for (i = 0; i < G_N_ELEMENTS (names); i++)
273 {
274 constraint = gtk_constraint_new (target: frame,
275 target_attribute: names[i].attr,
276 relation: GTK_CONSTRAINT_RELATION_EQ,
277 source: guide,
278 source_attribute: names[i].attr,
279 multiplier: 1.0, constant: 0.0,
280 strength: GTK_CONSTRAINT_STRENGTH_REQUIRED);
281 g_object_set_data (G_OBJECT (constraint), key: "internal", data: (char *)"yes");
282 gtk_constraint_layout_add_constraint (layout, constraint);
283 g_object_set_data (G_OBJECT (guide), key: names[i].name, data: constraint);
284 }
285
286 update_weak_position (self: view, child: frame, x: 150, y: 150);
287}
288
289void
290constraint_view_remove_guide (ConstraintView *view,
291 GtkConstraintGuide *guide)
292{
293 GtkConstraintLayout *layout;
294 GtkWidget *frame;
295 GtkConstraint *constraint;
296 const char *names[] = {
297 "left-constraint",
298 "top-constraint",
299 "width-constraint",
300 "height-constraint"
301 };
302 int i;
303
304 layout = GTK_CONSTRAINT_LAYOUT (ptr: gtk_widget_get_layout_manager (GTK_WIDGET (view)));
305
306 for (i = 0; i < G_N_ELEMENTS (names); i++)
307 {
308 constraint = (GtkConstraint*)g_object_get_data (G_OBJECT (guide), key: names[i]);
309 gtk_constraint_layout_remove_constraint (layout, constraint);
310 }
311
312 frame = (GtkWidget *)g_object_get_data (G_OBJECT (guide), key: "frame");
313 update_weak_position (self: view, child: frame, x: -100, y: -100);
314 gtk_widget_unparent (widget: frame);
315
316 gtk_constraint_layout_remove_guide (layout, guide);
317}
318
319void
320constraint_view_add_constraint (ConstraintView *view,
321 GtkConstraint *constraint)
322{
323 GtkLayoutManager *manager;
324
325 manager = gtk_widget_get_layout_manager (GTK_WIDGET (view));
326 gtk_constraint_layout_add_constraint (layout: GTK_CONSTRAINT_LAYOUT (ptr: manager),
327 g_object_ref (constraint));
328}
329
330void
331constraint_view_remove_constraint (ConstraintView *view,
332 GtkConstraint *constraint)
333{
334 GtkLayoutManager *manager;
335
336 manager = gtk_widget_get_layout_manager (GTK_WIDGET (view));
337 gtk_constraint_layout_remove_constraint (layout: GTK_CONSTRAINT_LAYOUT (ptr: manager),
338 constraint);
339}
340
341GListModel *
342constraint_view_get_model (ConstraintView *view)
343{
344 return view->model;
345}
346

source code of gtk/demos/constraint-editor/constraint-view.c