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-align-constraint
27  * @Title: ClutterAlignConstraint
28  * @Short_Description: A constraint aligning the position of an actor
29  *
30  * #ClutterAlignConstraint is a #ClutterConstraint that aligns the position
31  * of the #ClutterActor to which it is applied to the size of another
32  * #ClutterActor using an alignment factor
33  *
34  * #ClutterAlignConstraint is available since Clutter 1.4
35  */
36 
37 #ifdef HAVE_CONFIG_H
38 #include "clutter-build-config.h"
39 #endif
40 
41 #include "clutter-align-constraint.h"
42 
43 #include "clutter-actor-meta-private.h"
44 #include "clutter-actor-private.h"
45 #include "clutter-constraint.h"
46 #include "clutter-debug.h"
47 #include "clutter-enum-types.h"
48 #include "clutter-private.h"
49 
50 #include <math.h>
51 
52 #define CLUTTER_ALIGN_CONSTRAINT_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), CLUTTER_TYPE_ALIGN_CONSTRAINT, ClutterAlignConstraintClass))
53 #define CLUTTER_IS_ALIGN_CONSTRAINT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CLUTTER_TYPE_ALIGN_CONSTRAINT))
54 #define CLUTTER_ALIGN_CONSTRAINT_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), CLUTTER_TYPE_ALIGN_CONSTRAINT, ClutterAlignConstraintClass))
55 
56 struct _ClutterAlignConstraint
57 {
58   ClutterConstraint parent_instance;
59 
60   ClutterActor *actor;
61   ClutterActor *source;
62   ClutterAlignAxis align_axis;
63   gfloat factor;
64 };
65 
66 struct _ClutterAlignConstraintClass
67 {
68   ClutterConstraintClass parent_class;
69 };
70 
71 enum
72 {
73   PROP_0,
74 
75   PROP_SOURCE,
76   PROP_ALIGN_AXIS,
77   PROP_FACTOR,
78 
79   PROP_LAST
80 };
81 
82 static GParamSpec *obj_props[PROP_LAST];
83 
84 G_DEFINE_TYPE (ClutterAlignConstraint,
85                clutter_align_constraint,
86                CLUTTER_TYPE_CONSTRAINT);
87 
88 static void
source_position_changed(ClutterActor * actor,const ClutterActorBox * allocation,ClutterAllocationFlags flags,ClutterAlignConstraint * align)89 source_position_changed (ClutterActor           *actor,
90                          const ClutterActorBox  *allocation,
91                          ClutterAllocationFlags  flags,
92                          ClutterAlignConstraint *align)
93 {
94   if (align->actor != NULL)
95     clutter_actor_queue_relayout (align->actor);
96 }
97 
98 static void
source_destroyed(ClutterActor * actor,ClutterAlignConstraint * align)99 source_destroyed (ClutterActor           *actor,
100                   ClutterAlignConstraint *align)
101 {
102   align->source = NULL;
103 }
104 
105 static void
clutter_align_constraint_set_actor(ClutterActorMeta * meta,ClutterActor * new_actor)106 clutter_align_constraint_set_actor (ClutterActorMeta *meta,
107                                     ClutterActor     *new_actor)
108 {
109   ClutterAlignConstraint *align = CLUTTER_ALIGN_CONSTRAINT (meta);
110   ClutterActorMetaClass *parent;
111 
112   if (new_actor != NULL &&
113       align->source != NULL &&
114       clutter_actor_contains (new_actor, align->source))
115     {
116       g_warning (G_STRLOC ": The source actor '%s' is contained "
117                  "by the actor '%s' associated to the constraint "
118                  "'%s'",
119                  _clutter_actor_get_debug_name (align->source),
120                  _clutter_actor_get_debug_name (new_actor),
121                  _clutter_actor_meta_get_debug_name (meta));
122       return;
123     }
124 
125   /* store the pointer to the actor, for later use */
126   align->actor = new_actor;
127 
128   parent = CLUTTER_ACTOR_META_CLASS (clutter_align_constraint_parent_class);
129   parent->set_actor (meta, new_actor);
130 }
131 
132 static void
clutter_align_constraint_update_allocation(ClutterConstraint * constraint,ClutterActor * actor,ClutterActorBox * allocation)133 clutter_align_constraint_update_allocation (ClutterConstraint *constraint,
134                                             ClutterActor      *actor,
135                                             ClutterActorBox   *allocation)
136 {
137   ClutterAlignConstraint *align = CLUTTER_ALIGN_CONSTRAINT (constraint);
138   gfloat source_width, source_height;
139   gfloat actor_width, actor_height;
140   gfloat source_x, source_y;
141 
142   if (align->source == NULL)
143     return;
144 
145   clutter_actor_box_get_size (allocation, &actor_width, &actor_height);
146 
147   clutter_actor_get_position (align->source, &source_x, &source_y);
148   clutter_actor_get_size (align->source, &source_width, &source_height);
149 
150   switch (align->align_axis)
151     {
152     case CLUTTER_ALIGN_X_AXIS:
153       allocation->x1 = ((source_width - actor_width) * align->factor)
154                      + source_x;
155       allocation->x2 = allocation->x1 + actor_width;
156       break;
157 
158     case CLUTTER_ALIGN_Y_AXIS:
159       allocation->y1 = ((source_height - actor_height) * align->factor)
160                      + source_y;
161       allocation->y2 = allocation->y1 + actor_height;
162       break;
163 
164     case CLUTTER_ALIGN_BOTH:
165       allocation->x1 = ((source_width - actor_width) * align->factor)
166                      + source_x;
167       allocation->y1 = ((source_height - actor_height) * align->factor)
168                      + source_y;
169       allocation->x2 = allocation->x1 + actor_width;
170       allocation->y2 = allocation->y1 + actor_height;
171       break;
172 
173     default:
174       g_assert_not_reached ();
175       break;
176     }
177 
178   clutter_actor_box_clamp_to_pixel (allocation);
179 }
180 
181 static void
clutter_align_constraint_dispose(GObject * gobject)182 clutter_align_constraint_dispose (GObject *gobject)
183 {
184   ClutterAlignConstraint *align = CLUTTER_ALIGN_CONSTRAINT (gobject);
185 
186   if (align->source != NULL)
187     {
188       g_signal_handlers_disconnect_by_func (align->source,
189                                             G_CALLBACK (source_destroyed),
190                                             align);
191       g_signal_handlers_disconnect_by_func (align->source,
192                                             G_CALLBACK (source_position_changed),
193                                             align);
194       align->source = NULL;
195     }
196 
197   G_OBJECT_CLASS (clutter_align_constraint_parent_class)->dispose (gobject);
198 }
199 
200 static void
clutter_align_constraint_set_property(GObject * gobject,guint prop_id,const GValue * value,GParamSpec * pspec)201 clutter_align_constraint_set_property (GObject      *gobject,
202                                        guint         prop_id,
203                                        const GValue *value,
204                                        GParamSpec   *pspec)
205 {
206   ClutterAlignConstraint *align = CLUTTER_ALIGN_CONSTRAINT (gobject);
207 
208   switch (prop_id)
209     {
210     case PROP_SOURCE:
211       clutter_align_constraint_set_source (align, g_value_get_object (value));
212       break;
213 
214     case PROP_ALIGN_AXIS:
215       clutter_align_constraint_set_align_axis (align, g_value_get_enum (value));
216       break;
217 
218     case PROP_FACTOR:
219       clutter_align_constraint_set_factor (align, g_value_get_float (value));
220       break;
221 
222     default:
223       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
224       break;
225     }
226 }
227 
228 static void
clutter_align_constraint_get_property(GObject * gobject,guint prop_id,GValue * value,GParamSpec * pspec)229 clutter_align_constraint_get_property (GObject    *gobject,
230                                        guint       prop_id,
231                                        GValue     *value,
232                                        GParamSpec *pspec)
233 {
234   ClutterAlignConstraint *align = CLUTTER_ALIGN_CONSTRAINT (gobject);
235 
236   switch (prop_id)
237     {
238     case PROP_SOURCE:
239       g_value_set_object (value, align->source);
240       break;
241 
242     case PROP_ALIGN_AXIS:
243       g_value_set_enum (value, align->align_axis);
244       break;
245 
246     case PROP_FACTOR:
247       g_value_set_float (value, align->factor);
248       break;
249 
250     default:
251       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
252       break;
253     }
254 }
255 
256 static void
clutter_align_constraint_class_init(ClutterAlignConstraintClass * klass)257 clutter_align_constraint_class_init (ClutterAlignConstraintClass *klass)
258 {
259   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
260   ClutterActorMetaClass *meta_class = CLUTTER_ACTOR_META_CLASS (klass);
261   ClutterConstraintClass *constraint_class = CLUTTER_CONSTRAINT_CLASS (klass);
262 
263   meta_class->set_actor = clutter_align_constraint_set_actor;
264 
265   constraint_class->update_allocation = clutter_align_constraint_update_allocation;
266 
267   /**
268    * ClutterAlignConstraint:source:
269    *
270    * The #ClutterActor used as the source for the alignment.
271    *
272    * The #ClutterActor must not be a child or a grandchild of the actor
273    * using the constraint.
274    *
275    * Since: 1.4
276    */
277   obj_props[PROP_SOURCE] =
278     g_param_spec_object ("source",
279                            P_("Source"),
280                            P_("The source of the alignment"),
281                            CLUTTER_TYPE_ACTOR,
282                            CLUTTER_PARAM_READWRITE | G_PARAM_CONSTRUCT);
283 
284   /**
285    * ClutterAlignConstraint:align-axis:
286    *
287    * The axis to be used to compute the alignment
288    *
289    * Since: 1.4
290    */
291   obj_props[PROP_ALIGN_AXIS] =
292     g_param_spec_enum ("align-axis",
293                        P_("Align Axis"),
294                        P_("The axis to align the position to"),
295                        CLUTTER_TYPE_ALIGN_AXIS,
296                        CLUTTER_ALIGN_X_AXIS,
297                        CLUTTER_PARAM_READWRITE | G_PARAM_CONSTRUCT);
298 
299   /**
300    * ClutterAlignConstraint:factor:
301    *
302    * The alignment factor, as a normalized value between 0.0 and 1.0
303    *
304    * The factor depends on the #ClutterAlignConstraint:align-axis property:
305    * with an align-axis value of %CLUTTER_ALIGN_X_AXIS, 0.0 means left and
306    * 1.0 means right; with a value of %CLUTTER_ALIGN_Y_AXIS, 0.0 means top
307    * and 1.0 means bottom.
308    *
309    * Since: 1.4
310    */
311   obj_props[PROP_FACTOR] =
312     g_param_spec_float ("factor",
313                         P_("Factor"),
314                         P_("The alignment factor, between 0.0 and 1.0"),
315                         0.0, 1.0,
316                         0.0,
317                         CLUTTER_PARAM_READWRITE | G_PARAM_CONSTRUCT);
318 
319   gobject_class->dispose = clutter_align_constraint_dispose;
320   gobject_class->set_property = clutter_align_constraint_set_property;
321   gobject_class->get_property = clutter_align_constraint_get_property;
322   g_object_class_install_properties (gobject_class, PROP_LAST, obj_props);
323 }
324 
325 static void
clutter_align_constraint_init(ClutterAlignConstraint * self)326 clutter_align_constraint_init (ClutterAlignConstraint *self)
327 {
328   self->actor = NULL;
329   self->source = NULL;
330   self->align_axis = CLUTTER_ALIGN_X_AXIS;
331   self->factor = 0.0f;
332 }
333 
334 /**
335  * clutter_align_constraint_new:
336  * @source: (allow-none): the #ClutterActor to use as the source of the
337  *   alignment, or %NULL
338  * @axis: the axis to be used to compute the alignment
339  * @factor: the alignment factor, between 0.0 and 1.0
340  *
341  * Creates a new constraint, aligning a #ClutterActor's position with
342  * regards of the size of the actor to @source, with the given
343  * alignment @factor
344  *
345  * Return value: the newly created #ClutterAlignConstraint
346  *
347  * Since: 1.4
348  */
349 ClutterConstraint *
clutter_align_constraint_new(ClutterActor * source,ClutterAlignAxis axis,gfloat factor)350 clutter_align_constraint_new (ClutterActor     *source,
351                               ClutterAlignAxis  axis,
352                               gfloat            factor)
353 {
354   g_return_val_if_fail (source == NULL || CLUTTER_IS_ACTOR (source), NULL);
355 
356   return g_object_new (CLUTTER_TYPE_ALIGN_CONSTRAINT,
357                        "source", source,
358                        "align-axis", axis,
359                        "factor", factor,
360                        NULL);
361 }
362 
363 /**
364  * clutter_align_constraint_set_source:
365  * @align: a #ClutterAlignConstraint
366  * @source: (allow-none): a #ClutterActor, or %NULL to unset the source
367  *
368  * Sets the source of the alignment constraint
369  *
370  * Since: 1.4
371  */
372 void
clutter_align_constraint_set_source(ClutterAlignConstraint * align,ClutterActor * source)373 clutter_align_constraint_set_source (ClutterAlignConstraint *align,
374                                      ClutterActor           *source)
375 {
376   ClutterActor *old_source, *actor;
377   ClutterActorMeta *meta;
378 
379   g_return_if_fail (CLUTTER_IS_ALIGN_CONSTRAINT (align));
380   g_return_if_fail (source == NULL || CLUTTER_IS_ACTOR (source));
381 
382   if (align->source == source)
383     return;
384 
385   meta = CLUTTER_ACTOR_META (align);
386   actor = clutter_actor_meta_get_actor (meta);
387   if (actor != NULL && source != NULL)
388     {
389       if (clutter_actor_contains (actor, source))
390         {
391           g_warning (G_STRLOC ": The source actor '%s' is contained "
392                      "by the actor '%s' associated to the constraint "
393                      "'%s'",
394                      _clutter_actor_get_debug_name (source),
395                      _clutter_actor_get_debug_name (actor),
396                      _clutter_actor_meta_get_debug_name (meta));
397           return;
398         }
399     }
400 
401   old_source = align->source;
402   if (old_source != NULL)
403     {
404       g_signal_handlers_disconnect_by_func (old_source,
405                                             G_CALLBACK (source_destroyed),
406                                             align);
407       g_signal_handlers_disconnect_by_func (old_source,
408                                             G_CALLBACK (source_position_changed),
409                                             align);
410     }
411 
412   align->source = source;
413   if (align->source != NULL)
414     {
415       g_signal_connect (align->source, "allocation-changed",
416                         G_CALLBACK (source_position_changed),
417                         align);
418       g_signal_connect (align->source, "destroy",
419                         G_CALLBACK (source_destroyed),
420                         align);
421 
422       if (align->actor != NULL)
423         clutter_actor_queue_relayout (align->actor);
424     }
425 
426   g_object_notify_by_pspec (G_OBJECT (align), obj_props[PROP_SOURCE]);
427 }
428 
429 /**
430  * clutter_align_constraint_get_source:
431  * @align: a #ClutterAlignConstraint
432  *
433  * Retrieves the source of the alignment
434  *
435  * Return value: (transfer none): the #ClutterActor used as the source
436  *   of the alignment
437  *
438  * Since: 1.4
439  */
440 ClutterActor *
clutter_align_constraint_get_source(ClutterAlignConstraint * align)441 clutter_align_constraint_get_source (ClutterAlignConstraint *align)
442 {
443   g_return_val_if_fail (CLUTTER_IS_ALIGN_CONSTRAINT (align), NULL);
444 
445   return align->source;
446 }
447 
448 /**
449  * clutter_align_constraint_set_align_axis:
450  * @align: a #ClutterAlignConstraint
451  * @axis: the axis to which the alignment refers to
452  *
453  * Sets the axis to which the alignment refers to
454  *
455  * Since: 1.4
456  */
457 void
clutter_align_constraint_set_align_axis(ClutterAlignConstraint * align,ClutterAlignAxis axis)458 clutter_align_constraint_set_align_axis (ClutterAlignConstraint *align,
459                                          ClutterAlignAxis        axis)
460 {
461   g_return_if_fail (CLUTTER_IS_ALIGN_CONSTRAINT (align));
462 
463   if (align->align_axis == axis)
464     return;
465 
466   align->align_axis = axis;
467 
468   if (align->actor != NULL)
469     clutter_actor_queue_relayout (align->actor);
470 
471   g_object_notify_by_pspec (G_OBJECT (align), obj_props[PROP_ALIGN_AXIS]);
472 }
473 
474 /**
475  * clutter_align_constraint_get_align_axis:
476  * @align: a #ClutterAlignConstraint
477  *
478  * Retrieves the value set using clutter_align_constraint_set_align_axis()
479  *
480  * Return value: the alignment axis
481  *
482  * Since: 1.4
483  */
484 ClutterAlignAxis
clutter_align_constraint_get_align_axis(ClutterAlignConstraint * align)485 clutter_align_constraint_get_align_axis (ClutterAlignConstraint *align)
486 {
487   g_return_val_if_fail (CLUTTER_IS_ALIGN_CONSTRAINT (align),
488                         CLUTTER_ALIGN_X_AXIS);
489 
490   return align->align_axis;
491 }
492 
493 /**
494  * clutter_align_constraint_set_factor:
495  * @align: a #ClutterAlignConstraint
496  * @factor: the alignment factor, between 0.0 and 1.0
497  *
498  * Sets the alignment factor of the constraint
499  *
500  * The factor depends on the #ClutterAlignConstraint:align-axis property
501  * and it is a value between 0.0 (meaning left, when
502  * #ClutterAlignConstraint:align-axis is set to %CLUTTER_ALIGN_X_AXIS; or
503  * meaning top, when #ClutterAlignConstraint:align-axis is set to
504  * %CLUTTER_ALIGN_Y_AXIS) and 1.0 (meaning right, when
505  * #ClutterAlignConstraint:align-axis is set to %CLUTTER_ALIGN_X_AXIS; or
506  * meaning bottom, when #ClutterAlignConstraint:align-axis is set to
507  * %CLUTTER_ALIGN_Y_AXIS). A value of 0.5 aligns in the middle in either
508  * cases
509  *
510  * Since: 1.4
511  */
512 void
clutter_align_constraint_set_factor(ClutterAlignConstraint * align,gfloat factor)513 clutter_align_constraint_set_factor (ClutterAlignConstraint *align,
514                                      gfloat                  factor)
515 {
516   g_return_if_fail (CLUTTER_IS_ALIGN_CONSTRAINT (align));
517 
518   align->factor = CLAMP (factor, 0.0, 1.0);
519 
520   if (align->actor != NULL)
521     clutter_actor_queue_relayout (align->actor);
522 
523   g_object_notify_by_pspec (G_OBJECT (align), obj_props[PROP_FACTOR]);
524 }
525 
526 /**
527  * clutter_align_constraint_get_factor:
528  * @align: a #ClutterAlignConstraint
529  *
530  * Retrieves the factor set using clutter_align_constraint_set_factor()
531  *
532  * Return value: the alignment factor
533  *
534  * Since: 1.4
535  */
536 gfloat
clutter_align_constraint_get_factor(ClutterAlignConstraint * align)537 clutter_align_constraint_get_factor (ClutterAlignConstraint *align)
538 {
539   g_return_val_if_fail (CLUTTER_IS_ALIGN_CONSTRAINT (align), 0.0);
540 
541   return align->factor;
542 }
543