Release Clutter 1.11.4 (snapshot)
[profile/ivi/clutter.git] / clutter / clutter-bind-constraint.c
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  * |[
40  * /&ast; source &ast;/
41  * rect[0] = clutter_rectangle_new_with_color (&amp;red_color);
42  * clutter_actor_set_position (rect[0], x_pos, y_pos);
43  * clutter_actor_set_size (rect[0], 100, 100);
44  *
45  * /&ast; second rectangle &ast;/
46  * rect[1] = clutter_rectangle_new_with_color (&amp;green_color);
47  * clutter_actor_set_size (rect[1], 100, 100);
48  * clutter_actor_set_opacity (rect[1], 0);
49  *
50  * constraint = clutter_bind_constraint_new (rect[0], CLUTTER_BIND_X, 0.0);
51  * clutter_actor_add_constraint_with_name (rect[1], "green-x", constraint);
52  * constraint = clutter_bind_constraint_new (rect[0], CLUTTER_BIND_Y, 0.0);
53  * clutter_actor_add_constraint_with_name (rect[1], "green-y", constraint);
54  *
55  * /&ast; third rectangle &ast;/
56  * rect[2] = clutter_rectangle_new_with_color (&amp;blue_color);
57  * clutter_actor_set_size (rect[2], 100, 100);
58  * clutter_actor_set_opacity (rect[2], 0);
59  *
60  * constraint = clutter_bind_constraint_new (rect[0], CLUTTER_BIND_X, 0.0);
61  * clutter_actor_add_constraint_with_name (rect[2], "blue-x", constraint);
62  * constraint = clutter_bind_constraint_new (rect[0], CLUTTER_BIND_Y, 0.0);
63  * clutter_actor_add_constraint_with_name (rect[2], "blue-y", constraint);
64  * ]|
65  *
66  * The following code animates the second and third rectangles to "expand"
67  * them horizontally from underneath the first rectangle:
68  *
69  * |[
70  * clutter_actor_animate (rect[1], CLUTTER_EASE_OUT_CUBIC, 250,
71  *                        "@constraints.green-x.offset", 100.0,
72  *                        "opacity", 255,
73  *                        NULL);
74  * clutter_actor_animate (rect[2], CLUTTER_EASE_OUT_CUBIC, 250,
75  *                        "@constraints.blue-x.offset", 200.0,
76  *                        "opacity", 255,
77  *                        NULL);
78  * ]|
79  *
80  * <example id="bind-constraint-example">
81  *   <title>Animating the offset property of ClutterBindConstraint</title>
82  *   <programlisting>
83  * <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" parse="text" href="../../../../examples/bind-constraint.c">
84  *   <xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback>
85  * </xi:include>
86  *   </programlisting>
87  *   <para>The example above creates eight rectangles and binds them to a
88  *   rectangle positioned in the center of the stage; when the user presses
89  *   the center rectangle, the #ClutterBindConstraint:offset property is
90  *   animated through the clutter_actor_animate() function to lay out the
91  *   eight rectangles around the center one. Pressing one of the outer
92  *   rectangles will animate the offset back to 0.</para>
93  * </example>
94  *
95  * #ClutterBindConstraint is available since Clutter 1.4
96  */
97
98 #ifdef HAVE_CONFIG_H
99 #include "config.h"
100 #endif
101
102 #include <math.h>
103
104 #include "clutter-bind-constraint.h"
105
106 #include "clutter-actor-meta-private.h"
107 #include "clutter-actor-private.h"
108 #include "clutter-constraint.h"
109 #include "clutter-debug.h"
110 #include "clutter-enum-types.h"
111 #include "clutter-private.h"
112
113 #define CLUTTER_BIND_CONSTRAINT_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), CLUTTER_TYPE_BIND_CONSTRAINT, ClutterBindConstraintClass))
114 #define CLUTTER_IS_BIND_CONSTRAINT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CLUTTER_TYPE_BIND_CONSTRAINT))
115 #define CLUTTER_BIND_CONSTRAINT_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), CLUTTER_TYPE_BIND_CONSTRAINT, ClutterBindConstraintClass))
116
117 struct _ClutterBindConstraint
118 {
119   ClutterConstraint parent_instance;
120
121   ClutterActor *actor;
122   ClutterActor *source;
123   ClutterBindCoordinate coordinate;
124   gfloat offset;
125 };
126
127 struct _ClutterBindConstraintClass
128 {
129   ClutterConstraintClass parent_class;
130 };
131
132 enum
133 {
134   PROP_0,
135
136   PROP_SOURCE,
137   PROP_COORDINATE,
138   PROP_OFFSET,
139
140   PROP_LAST
141 };
142
143 static GParamSpec *obj_props[PROP_LAST];
144
145 G_DEFINE_TYPE (ClutterBindConstraint,
146                clutter_bind_constraint,
147                CLUTTER_TYPE_CONSTRAINT);
148
149 static void
150 source_queue_relayout (ClutterActor          *source,
151                        ClutterBindConstraint *bind)
152 {
153   if (bind->actor != NULL)
154     clutter_actor_queue_relayout (bind->actor);
155 }
156
157 static void
158 source_destroyed (ClutterActor          *actor,
159                   ClutterBindConstraint *bind)
160 {
161   bind->source = NULL;
162 }
163
164 static void
165 clutter_bind_constraint_update_allocation (ClutterConstraint *constraint,
166                                            ClutterActor      *actor,
167                                            ClutterActorBox   *allocation)
168 {
169   ClutterBindConstraint *bind = CLUTTER_BIND_CONSTRAINT (constraint);
170   gfloat source_width, source_height;
171   gfloat actor_width, actor_height;
172   ClutterVertex source_position = { 0., };
173
174   if (bind->source == NULL)
175     return;
176
177   source_position.x = clutter_actor_get_x (bind->source);
178   source_position.y = clutter_actor_get_y (bind->source);
179   clutter_actor_get_size (bind->source, &source_width, &source_height);
180
181   clutter_actor_box_get_size (allocation, &actor_width, &actor_height);
182
183   switch (bind->coordinate)
184     {
185     case CLUTTER_BIND_X:
186       allocation->x1 = source_position.x + bind->offset;
187       allocation->x2 = allocation->x1 + actor_width;
188       break;
189
190     case CLUTTER_BIND_Y:
191       allocation->y1 = source_position.y + bind->offset;
192       allocation->y2 = allocation->y1 + actor_height;
193       break;
194
195     case CLUTTER_BIND_POSITION:
196       allocation->x1 = source_position.x + bind->offset;
197       allocation->y1 = source_position.y + bind->offset;
198       allocation->x2 = allocation->x1 + actor_width;
199       allocation->y2 = allocation->y1 + actor_height;
200       break;
201
202     case CLUTTER_BIND_WIDTH:
203       allocation->x2 = allocation->x1 + source_width + bind->offset;
204       break;
205
206     case CLUTTER_BIND_HEIGHT:
207       allocation->y2 = allocation->y1 + source_height + bind->offset;
208       break;
209
210     case CLUTTER_BIND_SIZE:
211       allocation->x2 = allocation->x1 + source_width + bind->offset;
212       allocation->y2 = allocation->y1 + source_height + bind->offset;
213       break;
214
215     case CLUTTER_BIND_ALL:
216       allocation->x1 = source_position.x + bind->offset;
217       allocation->y1 = source_position.y + bind->offset;
218       allocation->x2 = allocation->x1 + source_width + bind->offset;
219       allocation->y2 = allocation->y1 + source_height + bind->offset;
220       break;
221
222     default:
223       g_assert_not_reached ();
224       break;
225     }
226 }
227
228 static void
229 clutter_bind_constraint_set_actor (ClutterActorMeta *meta,
230                                    ClutterActor     *new_actor)
231 {
232   ClutterBindConstraint *bind = CLUTTER_BIND_CONSTRAINT (meta);
233   ClutterActorMetaClass *parent;
234
235   if (new_actor != NULL &&
236       bind->source != NULL &&
237       clutter_actor_contains (new_actor, bind->source))
238     {
239       g_warning (G_STRLOC ": The source actor '%s' is contained "
240                  "by the actor '%s' associated to the constraint "
241                  "'%s'",
242                  _clutter_actor_get_debug_name (bind->source),
243                  _clutter_actor_get_debug_name (new_actor),
244                  _clutter_actor_meta_get_debug_name (meta));
245       return;
246     }
247
248   /* store the pointer to the actor, for later use */
249   bind->actor = new_actor;
250
251   parent = CLUTTER_ACTOR_META_CLASS (clutter_bind_constraint_parent_class);
252   parent->set_actor (meta, new_actor);
253 }
254
255 static void
256 clutter_bind_constraint_dispose (GObject *gobject)
257 {
258   ClutterBindConstraint *bind = CLUTTER_BIND_CONSTRAINT (gobject);
259
260   if (bind->source != NULL)
261     {
262       g_signal_handlers_disconnect_by_func (bind->source,
263                                             G_CALLBACK (source_destroyed),
264                                             bind);
265       g_signal_handlers_disconnect_by_func (bind->source,
266                                             G_CALLBACK (source_queue_relayout),
267                                             bind);
268       bind->source = NULL;
269     }
270
271   G_OBJECT_CLASS (clutter_bind_constraint_parent_class)->dispose (gobject);
272 }
273
274 static void
275 clutter_bind_constraint_set_property (GObject      *gobject,
276                                       guint         prop_id,
277                                       const GValue *value,
278                                       GParamSpec   *pspec)
279 {
280   ClutterBindConstraint *bind = CLUTTER_BIND_CONSTRAINT (gobject);
281
282   switch (prop_id)
283     {
284     case PROP_SOURCE:
285       clutter_bind_constraint_set_source (bind, g_value_get_object (value));
286       break;
287
288     case PROP_COORDINATE:
289       clutter_bind_constraint_set_coordinate (bind, g_value_get_enum (value));
290       break;
291
292     case PROP_OFFSET:
293       clutter_bind_constraint_set_offset (bind, g_value_get_float (value));
294       break;
295
296     default:
297       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
298       break;
299     }
300 }
301
302 static void
303 clutter_bind_constraint_get_property (GObject    *gobject,
304                                       guint       prop_id,
305                                       GValue     *value,
306                                       GParamSpec *pspec)
307 {
308   ClutterBindConstraint *bind = CLUTTER_BIND_CONSTRAINT (gobject);
309
310   switch (prop_id)
311     {
312     case PROP_SOURCE:
313       g_value_set_object (value, bind->source);
314       break;
315
316     case PROP_COORDINATE:
317       g_value_set_enum (value, bind->coordinate);
318       break;
319
320     case PROP_OFFSET:
321       g_value_set_float (value, bind->offset);
322       break;
323
324     default:
325       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
326       break;
327     }
328 }
329
330 static void
331 clutter_bind_constraint_class_init (ClutterBindConstraintClass *klass)
332 {
333   ClutterActorMetaClass *meta_class = CLUTTER_ACTOR_META_CLASS (klass);
334   ClutterConstraintClass *constraint_class = CLUTTER_CONSTRAINT_CLASS (klass);
335   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
336
337   gobject_class->set_property = clutter_bind_constraint_set_property;
338   gobject_class->get_property = clutter_bind_constraint_get_property;
339   gobject_class->dispose = clutter_bind_constraint_dispose;
340
341   meta_class->set_actor = clutter_bind_constraint_set_actor;
342
343   constraint_class->update_allocation = clutter_bind_constraint_update_allocation;
344   /**
345    * ClutterBindConstraint:source:
346    *
347    * The #ClutterActor used as the source for the binding.
348    *
349    * The #ClutterActor must not be contained inside the actor associated
350    * to the constraint.
351    *
352    * Since: 1.4
353    */
354   obj_props[PROP_SOURCE] =
355     g_param_spec_object ("source",
356                          P_("Source"),
357                          P_("The source of the binding"),
358                          CLUTTER_TYPE_ACTOR,
359                          CLUTTER_PARAM_READWRITE | G_PARAM_CONSTRUCT);
360
361   /**
362    * ClutterBindConstraint:coordinate:
363    *
364    * The coordinate to be bound
365    *
366    * Since: 1.4
367    */
368   obj_props[PROP_COORDINATE] =
369     g_param_spec_enum ("coordinate",
370                        P_("Coordinate"),
371                        P_("The coordinate to bind"),
372                        CLUTTER_TYPE_BIND_COORDINATE,
373                        CLUTTER_BIND_X,
374                        CLUTTER_PARAM_READWRITE | G_PARAM_CONSTRUCT);
375
376   /**
377    * ClutterBindConstraint:offset:
378    *
379    * The offset, in pixels, to be applied to the binding
380    *
381    * Since: 1.4
382    */
383   obj_props[PROP_OFFSET] =
384     g_param_spec_float ("offset",
385                         P_("Offset"),
386                         P_("The offset in pixels to apply to the binding"),
387                         -G_MAXFLOAT, G_MAXFLOAT,
388                         0.0f,
389                         CLUTTER_PARAM_READWRITE | G_PARAM_CONSTRUCT);
390
391   g_object_class_install_properties (gobject_class,
392                                      PROP_LAST,
393                                      obj_props);
394 }
395
396 static void
397 clutter_bind_constraint_init (ClutterBindConstraint *self)
398 {
399   self->actor = NULL;
400   self->source = NULL;
401   self->coordinate = CLUTTER_BIND_X;
402   self->offset = 0.0f;
403 }
404
405 /**
406  * clutter_bind_constraint_new:
407  * @source: (allow-none): the #ClutterActor to use as the source of
408  *   the binding, or %NULL
409  * @coordinate: the coordinate to bind
410  * @offset: the offset to apply to the binding, in pixels
411  *
412  * Creates a new constraint, binding a #ClutterActor's position to
413  * the given @coordinate of the position of @source
414  *
415  * Return value: the newly created #ClutterBindConstraint
416  *
417  * Since: 1.4
418  */
419 ClutterConstraint *
420 clutter_bind_constraint_new (ClutterActor          *source,
421                              ClutterBindCoordinate  coordinate,
422                              gfloat                 offset)
423 {
424   g_return_val_if_fail (source == NULL || CLUTTER_IS_ACTOR (source), NULL);
425
426   return g_object_new (CLUTTER_TYPE_BIND_CONSTRAINT,
427                        "source", source,
428                        "coordinate", coordinate,
429                        "offset", offset,
430                        NULL);
431 }
432
433 /**
434  * clutter_bind_constraint_set_source:
435  * @constraint: a #ClutterBindConstraint
436  * @source: (allow-none): a #ClutterActor, or %NULL to unset the source
437  *
438  * Sets the source #ClutterActor for the constraint
439  *
440  * Since: 1.4
441  */
442 void
443 clutter_bind_constraint_set_source (ClutterBindConstraint *constraint,
444                                     ClutterActor          *source)
445 {
446   ClutterActor *old_source, *actor;
447   ClutterActorMeta *meta;
448
449   g_return_if_fail (CLUTTER_IS_BIND_CONSTRAINT (constraint));
450   g_return_if_fail (source == NULL || CLUTTER_IS_ACTOR (source));
451
452   if (constraint->source == source)
453     return;
454
455   meta = CLUTTER_ACTOR_META (constraint);
456   actor = clutter_actor_meta_get_actor (meta);
457   if (source != NULL && actor != NULL)
458     {
459       if (clutter_actor_contains (actor, source))
460         {
461           g_warning (G_STRLOC ": The source actor '%s' is contained "
462                      "by the actor '%s' associated to the constraint "
463                      "'%s'",
464                      _clutter_actor_get_debug_name (source),
465                      _clutter_actor_get_debug_name (actor),
466                      _clutter_actor_meta_get_debug_name (meta));
467           return;
468         }
469     }
470
471   old_source = constraint->source;
472   if (old_source != NULL)
473     {
474       g_signal_handlers_disconnect_by_func (old_source,
475                                             G_CALLBACK (source_destroyed),
476                                             constraint);
477       g_signal_handlers_disconnect_by_func (old_source,
478                                             G_CALLBACK (source_queue_relayout),
479                                             constraint);
480     }
481
482   constraint->source = source;
483   if (constraint->source != NULL)
484     {
485       g_signal_connect (constraint->source, "queue-relayout",
486                         G_CALLBACK (source_queue_relayout),
487                         constraint);
488       g_signal_connect (constraint->source, "destroy",
489                         G_CALLBACK (source_destroyed),
490                         constraint);
491
492       if (constraint->actor != NULL)
493         clutter_actor_queue_relayout (constraint->actor);
494     }
495
496   g_object_notify_by_pspec (G_OBJECT (constraint), obj_props[PROP_SOURCE]);
497 }
498
499 /**
500  * clutter_bind_constraint_get_source:
501  * @constraint: a #ClutterBindConstraint
502  *
503  * Retrieves the #ClutterActor set using clutter_bind_constraint_set_source()
504  *
505  * Return value: (transfer none): a pointer to the source actor
506  *
507  * Since: 1.4
508  */
509 ClutterActor *
510 clutter_bind_constraint_get_source (ClutterBindConstraint *constraint)
511 {
512   g_return_val_if_fail (CLUTTER_IS_BIND_CONSTRAINT (constraint), NULL);
513
514   return constraint->source;
515 }
516
517 /**
518  * clutter_bind_constraint_set_coordinate:
519  * @constraint: a #ClutterBindConstraint
520  * @coordinate: the coordinate to bind
521  *
522  * Sets the coordinate to bind in the constraint
523  *
524  * Since: 1.4
525  */
526 void
527 clutter_bind_constraint_set_coordinate (ClutterBindConstraint *constraint,
528                                         ClutterBindCoordinate  coordinate)
529 {
530   g_return_if_fail (CLUTTER_IS_BIND_CONSTRAINT (constraint));
531
532   if (constraint->coordinate == coordinate)
533     return;
534
535   constraint->coordinate = coordinate;
536
537   if (constraint->actor != NULL)
538     clutter_actor_queue_relayout (constraint->actor);
539
540   g_object_notify_by_pspec (G_OBJECT (constraint), obj_props[PROP_COORDINATE]);
541 }
542
543 /**
544  * clutter_bind_constraint_get_coordinate:
545  * @constraint: a #ClutterBindConstraint
546  *
547  * Retrieves the bound coordinate of the constraint
548  *
549  * Return value: the bound coordinate
550  *
551  * Since: 1.4
552  */
553 ClutterBindCoordinate
554 clutter_bind_constraint_get_coordinate (ClutterBindConstraint *constraint)
555 {
556   g_return_val_if_fail (CLUTTER_IS_BIND_CONSTRAINT (constraint),
557                         CLUTTER_BIND_X);
558
559   return constraint->coordinate;
560 }
561
562 /**
563  * clutter_bind_constraint_set_offset:
564  * @constraint: a #ClutterBindConstraint
565  * @offset: the offset to apply, in pixels
566  *
567  * Sets the offset to be applied to the constraint
568  *
569  * Since: 1.4
570  */
571 void
572 clutter_bind_constraint_set_offset (ClutterBindConstraint *constraint,
573                                     gfloat                 offset)
574 {
575   g_return_if_fail (CLUTTER_IS_BIND_CONSTRAINT (constraint));
576
577   if (fabs (constraint->offset - offset) < 0.00001f)
578     return;
579
580   constraint->offset = offset;
581
582   if (constraint->actor != NULL)
583     clutter_actor_queue_relayout (constraint->actor);
584
585   g_object_notify_by_pspec (G_OBJECT (constraint), obj_props[PROP_OFFSET]);
586 }
587
588 /**
589  * clutter_bind_constraint_get_offset:
590  * @constraint: a #ClutterBindConstraint
591  *
592  * Retrieves the offset set using clutter_bind_constraint_set_offset()
593  *
594  * Return value: the offset, in pixels
595  *
596  * Since: 1.4
597  */
598 gfloat
599 clutter_bind_constraint_get_offset (ClutterBindConstraint *bind)
600 {
601   g_return_val_if_fail (CLUTTER_IS_BIND_CONSTRAINT (bind), 0.0);
602
603   return bind->offset;
604 }