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