1 /*
2  * Clutter.
3  *
4  * An OpenGL based 'interactive canvas' library.
5  *
6  * Copyright (C) 2010  Intel Corporation.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
20  *
21  * Author:
22  *   Emmanuele Bassi <ebassi@linux.intel.com>
23  */
24 
25 /**
26  * SECTION:clutter-bind-constraint
27  * @Title: ClutterBindConstraint
28  * @Short_Description: A constraint binding the position or size of an actor
29  *
30  * #ClutterBindConstraint is a #ClutterConstraint that binds the
31  * position or the size of the #ClutterActor to which it is applied
32  * to the the position or the size of another #ClutterActor, or
33  * "source".
34  *
35  * An offset can be applied to the constraint, to avoid overlapping. The offset
36  * can also be animated. For instance, the following code will set up three
37  * actors to be bound to the same origin:
38  *
39  * |[<!-- language="C" -->
40  * // source
41  * rect[0] = clutter_actor_new ();
42  * clutter_actor_set_background_color (rect[0], &red_color);
43  * clutter_actor_set_position (rect[0], x_pos, y_pos);
44  * clutter_actor_set_size (rect[0], 100, 100);
45  *
46  * // second rectangle
47  * rect[1] = clutter_actor_new ();
48  * clutter_actor_set_background_color (rect[1], &green_color);
49  * clutter_actor_set_size (rect[1], 100, 100);
50  * clutter_actor_set_opacity (rect[1], 0);
51  *
52  * constraint = clutter_bind_constraint_new (rect[0], CLUTTER_BIND_X, 0.0);
53  * clutter_actor_add_constraint_with_name (rect[1], "green-x", constraint);
54  * constraint = clutter_bind_constraint_new (rect[0], CLUTTER_BIND_Y, 0.0);
55  * clutter_actor_add_constraint_with_name (rect[1], "green-y", constraint);
56  *
57  * // third rectangle
58  * rect[2] = clutter_actor_new ();
59  * clutter_actor_set_background_color (rect[2], &blue_color);
60  * clutter_actor_set_size (rect[2], 100, 100);
61  * clutter_actor_set_opacity (rect[2], 0);
62  *
63  * constraint = clutter_bind_constraint_new (rect[0], CLUTTER_BIND_X, 0.0);
64  * clutter_actor_add_constraint_with_name (rect[2], "blue-x", constraint);
65  * constraint = clutter_bind_constraint_new (rect[0], CLUTTER_BIND_Y, 0.0);
66  * clutter_actor_add_constraint_with_name (rect[2], "blue-y", constraint);
67  * ]|
68  *
69  * The following code animates the second and third rectangles to "expand"
70  * them horizontally from underneath the first rectangle:
71  *
72  * |[<!-- language="C" -->
73  * clutter_actor_animate (rect[1], CLUTTER_EASE_OUT_CUBIC, 250,
74  *                        "@constraints.green-x.offset", 100.0,
75  *                        "opacity", 255,
76  *                        NULL);
77  * clutter_actor_animate (rect[2], CLUTTER_EASE_OUT_CUBIC, 250,
78  *                        "@constraints.blue-x.offset", 200.0,
79  *                        "opacity", 255,
80  *                        NULL);
81  * ]|
82  *
83  * #ClutterBindConstraint is available since Clutter 1.4
84  */
85 
86 #include "clutter-build-config.h"
87 
88 #include <math.h>
89 
90 #include "clutter-bind-constraint.h"
91 
92 #include "clutter-actor-meta-private.h"
93 #include "clutter-actor-private.h"
94 #include "clutter-constraint.h"
95 #include "clutter-debug.h"
96 #include "clutter-enum-types.h"
97 #include "clutter-private.h"
98 
99 #define CLUTTER_BIND_CONSTRAINT_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), CLUTTER_TYPE_BIND_CONSTRAINT, ClutterBindConstraintClass))
100 #define CLUTTER_IS_BIND_CONSTRAINT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CLUTTER_TYPE_BIND_CONSTRAINT))
101 #define CLUTTER_BIND_CONSTRAINT_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), CLUTTER_TYPE_BIND_CONSTRAINT, ClutterBindConstraintClass))
102 
103 struct _ClutterBindConstraint
104 {
105   ClutterConstraint parent_instance;
106 
107   ClutterActor *actor;
108   ClutterActor *source;
109   ClutterBindCoordinate coordinate;
110   gfloat offset;
111 };
112 
113 struct _ClutterBindConstraintClass
114 {
115   ClutterConstraintClass parent_class;
116 };
117 
118 enum
119 {
120   PROP_0,
121 
122   PROP_SOURCE,
123   PROP_COORDINATE,
124   PROP_OFFSET,
125 
126   PROP_LAST
127 };
128 
129 static GParamSpec *obj_props[PROP_LAST];
130 
131 G_DEFINE_TYPE (ClutterBindConstraint,
132                clutter_bind_constraint,
133                CLUTTER_TYPE_CONSTRAINT);
134 
135 static void
source_queue_relayout(ClutterActor * source,ClutterBindConstraint * bind)136 source_queue_relayout (ClutterActor          *source,
137                        ClutterBindConstraint *bind)
138 {
139   if (bind->actor != NULL)
140     _clutter_actor_queue_only_relayout (bind->actor);
141 }
142 
143 static void
source_destroyed(ClutterActor * actor,ClutterBindConstraint * bind)144 source_destroyed (ClutterActor          *actor,
145                   ClutterBindConstraint *bind)
146 {
147   bind->source = NULL;
148 }
149 
150 static void
clutter_bind_constraint_update_preferred_size(ClutterConstraint * constraint,ClutterActor * actor,ClutterOrientation direction,float for_size,float * minimum_size,float * natural_size)151 clutter_bind_constraint_update_preferred_size (ClutterConstraint  *constraint,
152                                                ClutterActor       *actor,
153                                                ClutterOrientation  direction,
154                                                float               for_size,
155                                                float              *minimum_size,
156                                                float              *natural_size)
157 {
158   ClutterBindConstraint *bind = CLUTTER_BIND_CONSTRAINT (constraint);
159   float source_min, source_nat;
160 
161   if (bind->source == NULL)
162     return;
163 
164   /* only these bindings affect the preferred size */
165   if (!(bind->coordinate == CLUTTER_BIND_WIDTH ||
166         bind->coordinate == CLUTTER_BIND_HEIGHT ||
167         bind->coordinate == CLUTTER_BIND_SIZE ||
168         bind->coordinate == CLUTTER_BIND_ALL))
169     return;
170 
171   if (clutter_actor_contains (bind->source, actor))
172     return;
173 
174   switch (direction)
175     {
176     case CLUTTER_ORIENTATION_HORIZONTAL:
177       if (bind->coordinate != CLUTTER_BIND_HEIGHT)
178         {
179           clutter_actor_get_preferred_width (bind->source, for_size,
180                                              &source_min,
181                                              &source_nat);
182 
183           *minimum_size = source_min;
184           *natural_size = source_nat;
185         }
186       break;
187 
188     case CLUTTER_ORIENTATION_VERTICAL:
189       if (bind->coordinate != CLUTTER_BIND_WIDTH)
190         {
191           clutter_actor_get_preferred_height (bind->source, for_size,
192                                               &source_min,
193                                               &source_nat);
194 
195           *minimum_size = source_min;
196           *natural_size = source_nat;
197         }
198       break;
199     }
200 }
201 
202 static void
clutter_bind_constraint_update_allocation(ClutterConstraint * constraint,ClutterActor * actor,ClutterActorBox * allocation)203 clutter_bind_constraint_update_allocation (ClutterConstraint *constraint,
204                                            ClutterActor      *actor,
205                                            ClutterActorBox   *allocation)
206 {
207   ClutterBindConstraint *bind = CLUTTER_BIND_CONSTRAINT (constraint);
208   gfloat source_width, source_height;
209   gfloat actor_width, actor_height;
210   graphene_point3d_t source_position;
211 
212   source_position = GRAPHENE_POINT3D_INIT (0.f, 0.f, 0.f);
213 
214   if (bind->source == NULL)
215     return;
216 
217   source_position.x = clutter_actor_get_x (bind->source);
218   source_position.y = clutter_actor_get_y (bind->source);
219   clutter_actor_get_size (bind->source, &source_width, &source_height);
220 
221   clutter_actor_box_get_size (allocation, &actor_width, &actor_height);
222 
223   switch (bind->coordinate)
224     {
225     case CLUTTER_BIND_X:
226       allocation->x1 = source_position.x + bind->offset;
227       allocation->x2 = allocation->x1 + actor_width;
228       break;
229 
230     case CLUTTER_BIND_Y:
231       allocation->y1 = source_position.y + bind->offset;
232       allocation->y2 = allocation->y1 + actor_height;
233       break;
234 
235     case CLUTTER_BIND_POSITION:
236       allocation->x1 = source_position.x + bind->offset;
237       allocation->y1 = source_position.y + bind->offset;
238       allocation->x2 = allocation->x1 + actor_width;
239       allocation->y2 = allocation->y1 + actor_height;
240       break;
241 
242     case CLUTTER_BIND_WIDTH:
243       allocation->x2 = allocation->x1 + source_width + bind->offset;
244       break;
245 
246     case CLUTTER_BIND_HEIGHT:
247       allocation->y2 = allocation->y1 + source_height + bind->offset;
248       break;
249 
250     case CLUTTER_BIND_SIZE:
251       allocation->x2 = allocation->x1 + source_width + bind->offset;
252       allocation->y2 = allocation->y1 + source_height + bind->offset;
253       break;
254 
255     case CLUTTER_BIND_ALL:
256       allocation->x1 = source_position.x + bind->offset;
257       allocation->y1 = source_position.y + bind->offset;
258       allocation->x2 = allocation->x1 + source_width + bind->offset;
259       allocation->y2 = allocation->y1 + source_height + bind->offset;
260       break;
261 
262     default:
263       g_assert_not_reached ();
264       break;
265     }
266 
267   clutter_actor_box_clamp_to_pixel (allocation);
268 }
269 
270 static void
clutter_bind_constraint_set_actor(ClutterActorMeta * meta,ClutterActor * new_actor)271 clutter_bind_constraint_set_actor (ClutterActorMeta *meta,
272                                    ClutterActor     *new_actor)
273 {
274   ClutterBindConstraint *bind = CLUTTER_BIND_CONSTRAINT (meta);
275   ClutterActorMetaClass *parent;
276 
277   if (new_actor != NULL &&
278       bind->source != NULL &&
279       clutter_actor_contains (new_actor, bind->source))
280     {
281       g_warning (G_STRLOC ": The source actor '%s' is contained "
282                  "by the actor '%s' associated to the constraint "
283                  "'%s'",
284                  _clutter_actor_get_debug_name (bind->source),
285                  _clutter_actor_get_debug_name (new_actor),
286                  _clutter_actor_meta_get_debug_name (meta));
287       return;
288     }
289 
290   /* store the pointer to the actor, for later use */
291   bind->actor = new_actor;
292 
293   parent = CLUTTER_ACTOR_META_CLASS (clutter_bind_constraint_parent_class);
294   parent->set_actor (meta, new_actor);
295 }
296 
297 static void
clutter_bind_constraint_dispose(GObject * gobject)298 clutter_bind_constraint_dispose (GObject *gobject)
299 {
300   ClutterBindConstraint *bind = CLUTTER_BIND_CONSTRAINT (gobject);
301 
302   if (bind->source != NULL)
303     {
304       g_signal_handlers_disconnect_by_func (bind->source,
305                                             G_CALLBACK (source_destroyed),
306                                             bind);
307       g_signal_handlers_disconnect_by_func (bind->source,
308                                             G_CALLBACK (source_queue_relayout),
309                                             bind);
310       bind->source = NULL;
311     }
312 
313   G_OBJECT_CLASS (clutter_bind_constraint_parent_class)->dispose (gobject);
314 }
315 
316 static void
clutter_bind_constraint_set_property(GObject * gobject,guint prop_id,const GValue * value,GParamSpec * pspec)317 clutter_bind_constraint_set_property (GObject      *gobject,
318                                       guint         prop_id,
319                                       const GValue *value,
320                                       GParamSpec   *pspec)
321 {
322   ClutterBindConstraint *bind = CLUTTER_BIND_CONSTRAINT (gobject);
323 
324   switch (prop_id)
325     {
326     case PROP_SOURCE:
327       clutter_bind_constraint_set_source (bind, g_value_get_object (value));
328       break;
329 
330     case PROP_COORDINATE:
331       clutter_bind_constraint_set_coordinate (bind, g_value_get_enum (value));
332       break;
333 
334     case PROP_OFFSET:
335       clutter_bind_constraint_set_offset (bind, g_value_get_float (value));
336       break;
337 
338     default:
339       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
340       break;
341     }
342 }
343 
344 static void
clutter_bind_constraint_get_property(GObject * gobject,guint prop_id,GValue * value,GParamSpec * pspec)345 clutter_bind_constraint_get_property (GObject    *gobject,
346                                       guint       prop_id,
347                                       GValue     *value,
348                                       GParamSpec *pspec)
349 {
350   ClutterBindConstraint *bind = CLUTTER_BIND_CONSTRAINT (gobject);
351 
352   switch (prop_id)
353     {
354     case PROP_SOURCE:
355       g_value_set_object (value, bind->source);
356       break;
357 
358     case PROP_COORDINATE:
359       g_value_set_enum (value, bind->coordinate);
360       break;
361 
362     case PROP_OFFSET:
363       g_value_set_float (value, bind->offset);
364       break;
365 
366     default:
367       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
368       break;
369     }
370 }
371 
372 static void
clutter_bind_constraint_class_init(ClutterBindConstraintClass * klass)373 clutter_bind_constraint_class_init (ClutterBindConstraintClass *klass)
374 {
375   ClutterActorMetaClass *meta_class = CLUTTER_ACTOR_META_CLASS (klass);
376   ClutterConstraintClass *constraint_class = CLUTTER_CONSTRAINT_CLASS (klass);
377   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
378 
379   gobject_class->set_property = clutter_bind_constraint_set_property;
380   gobject_class->get_property = clutter_bind_constraint_get_property;
381   gobject_class->dispose = clutter_bind_constraint_dispose;
382 
383   meta_class->set_actor = clutter_bind_constraint_set_actor;
384 
385   constraint_class->update_allocation = clutter_bind_constraint_update_allocation;
386   constraint_class->update_preferred_size = clutter_bind_constraint_update_preferred_size;
387 
388   /**
389    * ClutterBindConstraint:source:
390    *
391    * The #ClutterActor used as the source for the binding.
392    *
393    * The #ClutterActor must not be contained inside the actor associated
394    * to the constraint.
395    *
396    * Since: 1.4
397    */
398   obj_props[PROP_SOURCE] =
399     g_param_spec_object ("source",
400                          P_("Source"),
401                          P_("The source of the binding"),
402                          CLUTTER_TYPE_ACTOR,
403                          CLUTTER_PARAM_READWRITE | G_PARAM_CONSTRUCT);
404 
405   /**
406    * ClutterBindConstraint:coordinate:
407    *
408    * The coordinate to be bound
409    *
410    * Since: 1.4
411    */
412   obj_props[PROP_COORDINATE] =
413     g_param_spec_enum ("coordinate",
414                        P_("Coordinate"),
415                        P_("The coordinate to bind"),
416                        CLUTTER_TYPE_BIND_COORDINATE,
417                        CLUTTER_BIND_X,
418                        CLUTTER_PARAM_READWRITE | G_PARAM_CONSTRUCT);
419 
420   /**
421    * ClutterBindConstraint:offset:
422    *
423    * The offset, in pixels, to be applied to the binding
424    *
425    * Since: 1.4
426    */
427   obj_props[PROP_OFFSET] =
428     g_param_spec_float ("offset",
429                         P_("Offset"),
430                         P_("The offset in pixels to apply to the binding"),
431                         -G_MAXFLOAT, G_MAXFLOAT,
432                         0.0f,
433                         CLUTTER_PARAM_READWRITE | G_PARAM_CONSTRUCT);
434 
435   g_object_class_install_properties (gobject_class,
436                                      PROP_LAST,
437                                      obj_props);
438 }
439 
440 static void
clutter_bind_constraint_init(ClutterBindConstraint * self)441 clutter_bind_constraint_init (ClutterBindConstraint *self)
442 {
443   self->actor = NULL;
444   self->source = NULL;
445   self->coordinate = CLUTTER_BIND_X;
446   self->offset = 0.0f;
447 }
448 
449 /**
450  * clutter_bind_constraint_new:
451  * @source: (allow-none): the #ClutterActor to use as the source of
452  *   the binding, or %NULL
453  * @coordinate: the coordinate to bind
454  * @offset: the offset to apply to the binding, in pixels
455  *
456  * Creates a new constraint, binding a #ClutterActor's position to
457  * the given @coordinate of the position of @source
458  *
459  * Return value: the newly created #ClutterBindConstraint
460  *
461  * Since: 1.4
462  */
463 ClutterConstraint *
clutter_bind_constraint_new(ClutterActor * source,ClutterBindCoordinate coordinate,gfloat offset)464 clutter_bind_constraint_new (ClutterActor          *source,
465                              ClutterBindCoordinate  coordinate,
466                              gfloat                 offset)
467 {
468   g_return_val_if_fail (source == NULL || CLUTTER_IS_ACTOR (source), NULL);
469 
470   return g_object_new (CLUTTER_TYPE_BIND_CONSTRAINT,
471                        "source", source,
472                        "coordinate", coordinate,
473                        "offset", offset,
474                        NULL);
475 }
476 
477 /**
478  * clutter_bind_constraint_set_source:
479  * @constraint: a #ClutterBindConstraint
480  * @source: (allow-none): a #ClutterActor, or %NULL to unset the source
481  *
482  * Sets the source #ClutterActor for the constraint
483  *
484  * Since: 1.4
485  */
486 void
clutter_bind_constraint_set_source(ClutterBindConstraint * constraint,ClutterActor * source)487 clutter_bind_constraint_set_source (ClutterBindConstraint *constraint,
488                                     ClutterActor          *source)
489 {
490   ClutterActor *old_source, *actor;
491   ClutterActorMeta *meta;
492 
493   g_return_if_fail (CLUTTER_IS_BIND_CONSTRAINT (constraint));
494   g_return_if_fail (source == NULL || CLUTTER_IS_ACTOR (source));
495 
496   if (constraint->source == source)
497     return;
498 
499   meta = CLUTTER_ACTOR_META (constraint);
500   actor = clutter_actor_meta_get_actor (meta);
501   if (source != NULL && actor != NULL)
502     {
503       if (clutter_actor_contains (actor, source))
504         {
505           g_warning (G_STRLOC ": The source actor '%s' is contained "
506                      "by the actor '%s' associated to the constraint "
507                      "'%s'",
508                      _clutter_actor_get_debug_name (source),
509                      _clutter_actor_get_debug_name (actor),
510                      _clutter_actor_meta_get_debug_name (meta));
511           return;
512         }
513     }
514 
515   old_source = constraint->source;
516   if (old_source != NULL)
517     {
518       g_signal_handlers_disconnect_by_func (old_source,
519                                             G_CALLBACK (source_destroyed),
520                                             constraint);
521       g_signal_handlers_disconnect_by_func (old_source,
522                                             G_CALLBACK (source_queue_relayout),
523                                             constraint);
524     }
525 
526   constraint->source = source;
527   if (constraint->source != NULL)
528     {
529       g_signal_connect (constraint->source, "queue-relayout",
530                         G_CALLBACK (source_queue_relayout),
531                         constraint);
532       g_signal_connect (constraint->source, "destroy",
533                         G_CALLBACK (source_destroyed),
534                         constraint);
535 
536       if (constraint->actor != NULL)
537         clutter_actor_queue_relayout (constraint->actor);
538     }
539 
540   g_object_notify_by_pspec (G_OBJECT (constraint), obj_props[PROP_SOURCE]);
541 }
542 
543 /**
544  * clutter_bind_constraint_get_source:
545  * @constraint: a #ClutterBindConstraint
546  *
547  * Retrieves the #ClutterActor set using clutter_bind_constraint_set_source()
548  *
549  * Return value: (transfer none): a pointer to the source actor
550  *
551  * Since: 1.4
552  */
553 ClutterActor *
clutter_bind_constraint_get_source(ClutterBindConstraint * constraint)554 clutter_bind_constraint_get_source (ClutterBindConstraint *constraint)
555 {
556   g_return_val_if_fail (CLUTTER_IS_BIND_CONSTRAINT (constraint), NULL);
557 
558   return constraint->source;
559 }
560 
561 /**
562  * clutter_bind_constraint_set_coordinate:
563  * @constraint: a #ClutterBindConstraint
564  * @coordinate: the coordinate to bind
565  *
566  * Sets the coordinate to bind in the constraint
567  *
568  * Since: 1.4
569  */
570 void
clutter_bind_constraint_set_coordinate(ClutterBindConstraint * constraint,ClutterBindCoordinate coordinate)571 clutter_bind_constraint_set_coordinate (ClutterBindConstraint *constraint,
572                                         ClutterBindCoordinate  coordinate)
573 {
574   g_return_if_fail (CLUTTER_IS_BIND_CONSTRAINT (constraint));
575 
576   if (constraint->coordinate == coordinate)
577     return;
578 
579   constraint->coordinate = coordinate;
580 
581   if (constraint->actor != NULL)
582     clutter_actor_queue_relayout (constraint->actor);
583 
584   g_object_notify_by_pspec (G_OBJECT (constraint), obj_props[PROP_COORDINATE]);
585 }
586 
587 /**
588  * clutter_bind_constraint_get_coordinate:
589  * @constraint: a #ClutterBindConstraint
590  *
591  * Retrieves the bound coordinate of the constraint
592  *
593  * Return value: the bound coordinate
594  *
595  * Since: 1.4
596  */
597 ClutterBindCoordinate
clutter_bind_constraint_get_coordinate(ClutterBindConstraint * constraint)598 clutter_bind_constraint_get_coordinate (ClutterBindConstraint *constraint)
599 {
600   g_return_val_if_fail (CLUTTER_IS_BIND_CONSTRAINT (constraint),
601                         CLUTTER_BIND_X);
602 
603   return constraint->coordinate;
604 }
605 
606 /**
607  * clutter_bind_constraint_set_offset:
608  * @constraint: a #ClutterBindConstraint
609  * @offset: the offset to apply, in pixels
610  *
611  * Sets the offset to be applied to the constraint
612  *
613  * Since: 1.4
614  */
615 void
clutter_bind_constraint_set_offset(ClutterBindConstraint * constraint,gfloat offset)616 clutter_bind_constraint_set_offset (ClutterBindConstraint *constraint,
617                                     gfloat                 offset)
618 {
619   g_return_if_fail (CLUTTER_IS_BIND_CONSTRAINT (constraint));
620 
621   if (fabs (constraint->offset - offset) < 0.00001f)
622     return;
623 
624   constraint->offset = offset;
625 
626   if (constraint->actor != NULL)
627     clutter_actor_queue_relayout (constraint->actor);
628 
629   g_object_notify_by_pspec (G_OBJECT (constraint), obj_props[PROP_OFFSET]);
630 }
631 
632 /**
633  * clutter_bind_constraint_get_offset:
634  * @constraint: a #ClutterBindConstraint
635  *
636  * Retrieves the offset set using clutter_bind_constraint_set_offset()
637  *
638  * Return value: the offset, in pixels
639  *
640  * Since: 1.4
641  */
642 gfloat
clutter_bind_constraint_get_offset(ClutterBindConstraint * bind)643 clutter_bind_constraint_get_offset (ClutterBindConstraint *bind)
644 {
645   g_return_val_if_fail (CLUTTER_IS_BIND_CONSTRAINT (bind), 0.0);
646 
647   return bind->offset;
648 }
649