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-snap-constraint
27  * @Title: ClutterSnapConstraint
28  * @Short_Description: A constraint snapping two actors together
29  *
30  * #ClutterSnapConstraint is a constraint the snaps the edges of two
31  * actors together, expanding the actor's allocation if necessary.
32  *
33  * An offset can be applied to the constraint, to provide spacing.
34  *
35  * #ClutterSnapConstraint is available since Clutter 1.6
36  */
37 
38 #ifdef HAVE_CONFIG_H
39 #include "clutter-build-config.h"
40 #endif
41 
42 #include <math.h>
43 
44 #include "clutter-snap-constraint.h"
45 
46 #include "clutter-actor-private.h"
47 #include "clutter-constraint.h"
48 #include "clutter-debug.h"
49 #include "clutter-enum-types.h"
50 #include "clutter-private.h"
51 
52 #define CLUTTER_SNAP_CONSTRAINT_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), CLUTTER_TYPE_SNAP_CONSTRAINT, ClutterSnapConstraintClass))
53 #define CLUTTER_IS_SNAP_CONSTRAINT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CLUTTER_TYPE_SNAP_CONSTRAINT))
54 #define CLUTTER_SNAP_CONSTRAINT_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), CLUTTER_TYPE_SNAP_CONSTRAINT, ClutterSnapConstraintClass))
55 
56 struct _ClutterSnapConstraint
57 {
58   ClutterConstraint parent_instance;
59 
60   ClutterActor *actor;
61   ClutterActor *source;
62 
63   ClutterSnapEdge from_edge;
64   ClutterSnapEdge to_edge;
65 
66   gfloat offset;
67 };
68 
69 struct _ClutterSnapConstraintClass
70 {
71   ClutterConstraintClass parent_class;
72 };
73 
74 enum
75 {
76   PROP_0,
77 
78   PROP_SOURCE,
79   PROP_FROM_EDGE,
80   PROP_TO_EDGE,
81   PROP_OFFSET,
82 
83   PROP_LAST
84 };
85 
86 G_DEFINE_TYPE (ClutterSnapConstraint,
87                clutter_snap_constraint,
88                CLUTTER_TYPE_CONSTRAINT);
89 
90 static GParamSpec *obj_props[PROP_LAST] = { NULL, };
91 
92 static void
source_queue_relayout(ClutterActor * source,ClutterSnapConstraint * constraint)93 source_queue_relayout (ClutterActor          *source,
94                        ClutterSnapConstraint *constraint)
95 {
96   if (constraint->actor != NULL)
97     _clutter_actor_queue_only_relayout (constraint->actor);
98 }
99 
100 static void
source_destroyed(ClutterActor * actor,ClutterSnapConstraint * constraint)101 source_destroyed (ClutterActor          *actor,
102                   ClutterSnapConstraint *constraint)
103 {
104   constraint->source = NULL;
105 }
106 
107 static inline void
warn_horizontal_edge(const gchar * edge,ClutterActor * actor,ClutterActor * source)108 warn_horizontal_edge (const gchar  *edge,
109                       ClutterActor *actor,
110                       ClutterActor *source)
111 {
112   g_warning (G_STRLOC ": the %s edge of actor '%s' can only be snapped "
113              "to either the right or the left edge of actor '%s'",
114              edge,
115              _clutter_actor_get_debug_name (actor),
116              _clutter_actor_get_debug_name (source));
117 }
118 
119 static inline void
warn_vertical_edge(const gchar * edge,ClutterActor * actor,ClutterActor * source)120 warn_vertical_edge (const gchar  *edge,
121                     ClutterActor *actor,
122                     ClutterActor *source)
123 {
124   g_warning (G_STRLOC ": the %s edge of actor '%s' can only "
125              "be snapped to the top or bottom edge of actor '%s'",
126              edge,
127              _clutter_actor_get_debug_name (actor),
128              _clutter_actor_get_debug_name (source));
129 }
130 
131 static void
clutter_snap_constraint_update_allocation(ClutterConstraint * constraint,ClutterActor * actor,ClutterActorBox * allocation)132 clutter_snap_constraint_update_allocation (ClutterConstraint *constraint,
133                                            ClutterActor      *actor,
134                                            ClutterActorBox   *allocation)
135 {
136   ClutterSnapConstraint *self = CLUTTER_SNAP_CONSTRAINT (constraint);
137   gfloat source_width, source_height;
138   gfloat source_x, source_y;
139   gfloat actor_width, actor_height;
140 
141   if (self->source == NULL)
142     return;
143 
144   clutter_actor_get_position (self->source, &source_x, &source_y);
145   clutter_actor_get_size (self->source, &source_width, &source_height);
146 
147   clutter_actor_box_get_size (allocation, &actor_width, &actor_height);
148 
149   switch (self->to_edge)
150     {
151     case CLUTTER_SNAP_EDGE_LEFT:
152       if (self->from_edge == CLUTTER_SNAP_EDGE_LEFT)
153         allocation->x1 = source_x + self->offset;
154       else if (self->from_edge == CLUTTER_SNAP_EDGE_RIGHT)
155         allocation->x2 = source_x + self->offset;
156       else
157         warn_horizontal_edge ("left", self->actor, self->source);
158       break;
159 
160     case CLUTTER_SNAP_EDGE_RIGHT:
161       if (self->from_edge == CLUTTER_SNAP_EDGE_RIGHT)
162         allocation->x2 = source_x + source_width + self->offset;
163       else if (self->from_edge == CLUTTER_SNAP_EDGE_LEFT)
164         allocation->x1 = source_x + source_width + self->offset;
165       else
166         warn_horizontal_edge ("right", self->actor, self->source);
167       break;
168 
169       break;
170 
171     case CLUTTER_SNAP_EDGE_TOP:
172       if (self->from_edge == CLUTTER_SNAP_EDGE_TOP)
173         allocation->y1 = source_y + self->offset;
174       else if (self->from_edge == CLUTTER_SNAP_EDGE_BOTTOM)
175         allocation->y2 = source_y + self->offset;
176       else
177         warn_vertical_edge ("top", self->actor, self->source);
178       break;
179 
180     case CLUTTER_SNAP_EDGE_BOTTOM:
181       if (self->from_edge == CLUTTER_SNAP_EDGE_BOTTOM)
182         allocation->y2 = source_y + source_height + self->offset;
183       else if (self->from_edge == CLUTTER_SNAP_EDGE_TOP)
184         allocation->y1 = source_y + source_height + self->offset;
185       else
186         warn_vertical_edge ("bottom", self->actor, self->source);
187       break;
188 
189     default:
190       g_assert_not_reached ();
191       break;
192     }
193 
194   if (allocation->x2 - allocation->x1 < 0)
195     allocation->x2 = allocation->x1;
196 
197   if (allocation->y2 - allocation->y1 < 0)
198     allocation->y2 = allocation->y1;
199 }
200 
201 static void
clutter_snap_constraint_set_actor(ClutterActorMeta * meta,ClutterActor * new_actor)202 clutter_snap_constraint_set_actor (ClutterActorMeta *meta,
203                                    ClutterActor     *new_actor)
204 {
205   ClutterSnapConstraint *self = CLUTTER_SNAP_CONSTRAINT (meta);
206   ClutterActorMetaClass *parent;
207 
208   /* store the pointer to the actor, for later use */
209   self->actor = new_actor;
210 
211   parent = CLUTTER_ACTOR_META_CLASS (clutter_snap_constraint_parent_class);
212   parent->set_actor (meta, new_actor);
213 }
214 
215 static void
clutter_snap_constraint_dispose(GObject * gobject)216 clutter_snap_constraint_dispose (GObject *gobject)
217 {
218   ClutterSnapConstraint *snap = CLUTTER_SNAP_CONSTRAINT (gobject);
219 
220   if (snap->source != NULL)
221     {
222       g_signal_handlers_disconnect_by_func (snap->source,
223                                             G_CALLBACK (source_destroyed),
224                                             snap);
225       g_signal_handlers_disconnect_by_func (snap->source,
226                                             G_CALLBACK (source_queue_relayout),
227                                             snap);
228       snap->source = NULL;
229     }
230 
231   G_OBJECT_CLASS (clutter_snap_constraint_parent_class)->dispose (gobject);
232 }
233 
234 static void
clutter_snap_constraint_set_property(GObject * gobject,guint prop_id,const GValue * value,GParamSpec * pspec)235 clutter_snap_constraint_set_property (GObject      *gobject,
236                                       guint         prop_id,
237                                       const GValue *value,
238                                       GParamSpec   *pspec)
239 {
240   ClutterSnapConstraint *self = CLUTTER_SNAP_CONSTRAINT (gobject);
241 
242   switch (prop_id)
243     {
244     case PROP_SOURCE:
245       clutter_snap_constraint_set_source (self, g_value_get_object (value));
246       break;
247 
248     case PROP_FROM_EDGE:
249       clutter_snap_constraint_set_edges (self,
250                                          g_value_get_enum (value),
251                                          self->to_edge);
252       break;
253 
254     case PROP_TO_EDGE:
255       clutter_snap_constraint_set_edges (self,
256                                          self->from_edge,
257                                          g_value_get_enum (value));
258       break;
259 
260     case PROP_OFFSET:
261       clutter_snap_constraint_set_offset (self, g_value_get_float (value));
262       break;
263 
264     default:
265       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
266       break;
267     }
268 }
269 
270 static void
clutter_snap_constraint_get_property(GObject * gobject,guint prop_id,GValue * value,GParamSpec * pspec)271 clutter_snap_constraint_get_property (GObject    *gobject,
272                                       guint       prop_id,
273                                       GValue     *value,
274                                       GParamSpec *pspec)
275 {
276   ClutterSnapConstraint *self = CLUTTER_SNAP_CONSTRAINT (gobject);
277 
278   switch (prop_id)
279     {
280     case PROP_SOURCE:
281       g_value_set_object (value, self->source);
282       break;
283 
284     case PROP_FROM_EDGE:
285       g_value_set_enum (value, self->from_edge);
286       break;
287 
288     case PROP_TO_EDGE:
289       g_value_set_enum (value, self->to_edge);
290       break;
291 
292     case PROP_OFFSET:
293       g_value_set_float (value, self->offset);
294       break;
295 
296     default:
297       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
298       break;
299     }
300 }
301 
302 static void
clutter_snap_constraint_class_init(ClutterSnapConstraintClass * klass)303 clutter_snap_constraint_class_init (ClutterSnapConstraintClass *klass)
304 {
305   ClutterActorMetaClass *meta_class = CLUTTER_ACTOR_META_CLASS (klass);
306   ClutterConstraintClass *constraint_class = CLUTTER_CONSTRAINT_CLASS (klass);
307   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
308 
309   meta_class->set_actor = clutter_snap_constraint_set_actor;
310 
311   constraint_class->update_allocation = clutter_snap_constraint_update_allocation;
312   /**
313    * ClutterSnapConstraint:source:
314    *
315    * The #ClutterActor used as the source for the constraint
316    *
317    * Since: 1.6
318    */
319   obj_props[PROP_SOURCE] =
320     g_param_spec_object ("source",
321                          P_("Source"),
322                          P_("The source of the constraint"),
323                          CLUTTER_TYPE_ACTOR,
324                          CLUTTER_PARAM_READWRITE | G_PARAM_CONSTRUCT);
325 
326   /**
327    * ClutterSnapConstraint:from-edge:
328    *
329    * The edge of the #ClutterActor that should be snapped
330    *
331    * Since: 1.6
332    */
333   obj_props[PROP_FROM_EDGE] =
334     g_param_spec_enum ("from-edge",
335                        P_("From Edge"),
336                        P_("The edge of the actor that should be snapped"),
337                        CLUTTER_TYPE_SNAP_EDGE,
338                        CLUTTER_SNAP_EDGE_RIGHT,
339                        CLUTTER_PARAM_READWRITE | G_PARAM_CONSTRUCT);
340 
341   /**
342    * ClutterSnapConstraint:to-edge:
343    *
344    * The edge of the #ClutterSnapConstraint:source that should be snapped
345    *
346    * Since: 1.6
347    */
348   obj_props[PROP_TO_EDGE] =
349     g_param_spec_enum ("to-edge",
350                        P_("To Edge"),
351                        P_("The edge of the source that should be snapped"),
352                        CLUTTER_TYPE_SNAP_EDGE,
353                        CLUTTER_SNAP_EDGE_RIGHT,
354                        CLUTTER_PARAM_READWRITE | G_PARAM_CONSTRUCT);
355 
356   /**
357    * ClutterSnapConstraint:offset:
358    *
359    * The offset, in pixels, between #ClutterSnapConstraint:from-edge
360    * and #ClutterSnapConstraint:to-edge
361    *
362    * Since: 1.6
363    */
364   obj_props[PROP_OFFSET] =
365     g_param_spec_float ("offset",
366                         P_("Offset"),
367                         P_("The offset in pixels to apply to the constraint"),
368                         -G_MAXFLOAT, G_MAXFLOAT,
369                         0.0f,
370                         CLUTTER_PARAM_READWRITE | G_PARAM_CONSTRUCT);
371 
372   gobject_class->dispose = clutter_snap_constraint_dispose;
373   gobject_class->set_property = clutter_snap_constraint_set_property;
374   gobject_class->get_property = clutter_snap_constraint_get_property;
375   g_object_class_install_properties (gobject_class, PROP_LAST, obj_props);
376 }
377 
378 static void
clutter_snap_constraint_init(ClutterSnapConstraint * self)379 clutter_snap_constraint_init (ClutterSnapConstraint *self)
380 {
381   self->actor = NULL;
382   self->source = NULL;
383 
384   self->from_edge = CLUTTER_SNAP_EDGE_RIGHT;
385   self->to_edge = CLUTTER_SNAP_EDGE_RIGHT;
386 
387   self->offset = 0.0f;
388 }
389 
390 /**
391  * clutter_snap_constraint_new:
392  * @source: (allow-none): the #ClutterActor to use as the source of
393  *   the constraint, or %NULL
394  * @from_edge: the edge of the actor to use in the constraint
395  * @to_edge: the edge of @source to use in the constraint
396  * @offset: the offset to apply to the constraint, in pixels
397  *
398  * Creates a new #ClutterSnapConstraint that will snap a #ClutterActor
399  * to the @edge of @source, with the given @offset.
400  *
401  * Return value: the newly created #ClutterSnapConstraint
402  *
403  * Since: 1.6
404  */
405 ClutterConstraint *
clutter_snap_constraint_new(ClutterActor * source,ClutterSnapEdge from_edge,ClutterSnapEdge to_edge,gfloat offset)406 clutter_snap_constraint_new (ClutterActor    *source,
407                              ClutterSnapEdge  from_edge,
408                              ClutterSnapEdge  to_edge,
409                              gfloat           offset)
410 {
411   g_return_val_if_fail (source == NULL || CLUTTER_IS_ACTOR (source), NULL);
412 
413   return g_object_new (CLUTTER_TYPE_SNAP_CONSTRAINT,
414                        "source", source,
415                        "from-edge", from_edge,
416                        "to-edge", to_edge,
417                        "offset", offset,
418                        NULL);
419 }
420 
421 /**
422  * clutter_snap_constraint_set_source:
423  * @constraint: a #ClutterSnapConstraint
424  * @source: (allow-none): a #ClutterActor, or %NULL to unset the source
425  *
426  * Sets the source #ClutterActor for the constraint
427  *
428  * Since: 1.6
429  */
430 void
clutter_snap_constraint_set_source(ClutterSnapConstraint * constraint,ClutterActor * source)431 clutter_snap_constraint_set_source (ClutterSnapConstraint *constraint,
432                                     ClutterActor          *source)
433 {
434   ClutterActor *old_source;
435 
436   g_return_if_fail (CLUTTER_IS_SNAP_CONSTRAINT (constraint));
437   g_return_if_fail (source == NULL || CLUTTER_IS_ACTOR (source));
438 
439   if (constraint->source == source)
440     return;
441 
442   old_source = constraint->source;
443   if (old_source != NULL)
444     {
445       g_signal_handlers_disconnect_by_func (old_source,
446                                             G_CALLBACK (source_destroyed),
447                                             constraint);
448       g_signal_handlers_disconnect_by_func (old_source,
449                                             G_CALLBACK (source_queue_relayout),
450                                             constraint);
451     }
452 
453   constraint->source = source;
454   if (constraint->source != NULL)
455     {
456       g_signal_connect (constraint->source, "queue-relayout",
457                         G_CALLBACK (source_queue_relayout),
458                         constraint);
459       g_signal_connect (constraint->source, "destroy",
460                         G_CALLBACK (source_destroyed),
461                         constraint);
462 
463       if (constraint->actor != NULL)
464         clutter_actor_queue_relayout (constraint->actor);
465     }
466 
467   g_object_notify_by_pspec (G_OBJECT (constraint), obj_props[PROP_SOURCE]);
468 }
469 
470 /**
471  * clutter_snap_constraint_get_source:
472  * @constraint: a #ClutterSnapConstraint
473  *
474  * Retrieves the #ClutterActor set using clutter_snap_constraint_set_source()
475  *
476  * Return value: (transfer none): a pointer to the source actor
477  *
478  * Since: 1.6
479  */
480 ClutterActor *
clutter_snap_constraint_get_source(ClutterSnapConstraint * constraint)481 clutter_snap_constraint_get_source (ClutterSnapConstraint *constraint)
482 {
483   g_return_val_if_fail (CLUTTER_IS_SNAP_CONSTRAINT (constraint), NULL);
484 
485   return constraint->source;
486 }
487 
488 /**
489  * clutter_snap_constraint_set_edges:
490  * @constraint: a #ClutterSnapConstraint
491  * @from_edge: the edge on the actor
492  * @to_edge: the edge on the source
493  *
494  * Sets the edges to be used by the @constraint
495  *
496  * The @from_edge is the edge on the #ClutterActor to which @constraint
497  * has been added. The @to_edge is the edge of the #ClutterActor inside
498  * the #ClutterSnapConstraint:source property.
499  *
500  * Since: 1.6
501  */
502 void
clutter_snap_constraint_set_edges(ClutterSnapConstraint * constraint,ClutterSnapEdge from_edge,ClutterSnapEdge to_edge)503 clutter_snap_constraint_set_edges (ClutterSnapConstraint *constraint,
504                                    ClutterSnapEdge        from_edge,
505                                    ClutterSnapEdge        to_edge)
506 {
507   gboolean from_changed = FALSE, to_changed = FALSE;
508 
509   g_return_if_fail (CLUTTER_IS_SNAP_CONSTRAINT (constraint));
510 
511   g_object_freeze_notify (G_OBJECT (constraint));
512 
513   if (constraint->from_edge != from_edge)
514     {
515       constraint->from_edge = from_edge;
516       g_object_notify_by_pspec (G_OBJECT (constraint),
517                                 obj_props[PROP_FROM_EDGE]);
518       from_changed = TRUE;
519     }
520 
521   if (constraint->to_edge != to_edge)
522     {
523       constraint->to_edge = to_edge;
524       g_object_notify_by_pspec (G_OBJECT (constraint),
525                                 obj_props[PROP_TO_EDGE]);
526       to_changed = TRUE;
527     }
528 
529   if ((from_changed || to_changed) &&
530       constraint->actor != NULL)
531     {
532       clutter_actor_queue_relayout (constraint->actor);
533     }
534 
535   g_object_thaw_notify (G_OBJECT (constraint));
536 }
537 
538 /**
539  * clutter_snap_constraint_get_edges:
540  * @constraint: a #ClutterSnapConstraint
541  * @from_edge: (out): return location for the actor's edge, or %NULL
542  * @to_edge: (out): return location for the source's edge, or %NULL
543  *
544  * Retrieves the edges used by the @constraint
545  *
546  * Since: 1.6
547  */
548 void
clutter_snap_constraint_get_edges(ClutterSnapConstraint * constraint,ClutterSnapEdge * from_edge,ClutterSnapEdge * to_edge)549 clutter_snap_constraint_get_edges (ClutterSnapConstraint *constraint,
550                                    ClutterSnapEdge       *from_edge,
551                                    ClutterSnapEdge       *to_edge)
552 {
553   g_return_if_fail (CLUTTER_IS_SNAP_CONSTRAINT (constraint));
554 
555   if (from_edge)
556     *from_edge = constraint->from_edge;
557 
558   if (to_edge)
559     *to_edge = constraint->to_edge;
560 }
561 
562 /**
563  * clutter_snap_constraint_set_offset:
564  * @constraint: a #ClutterSnapConstraint
565  * @offset: the offset to apply, in pixels
566  *
567  * Sets the offset to be applied to the constraint
568  *
569  * Since: 1.6
570  */
571 void
clutter_snap_constraint_set_offset(ClutterSnapConstraint * constraint,gfloat offset)572 clutter_snap_constraint_set_offset (ClutterSnapConstraint *constraint,
573                                     gfloat                 offset)
574 {
575   g_return_if_fail (CLUTTER_IS_SNAP_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_snap_constraint_get_offset:
590  * @constraint: a #ClutterSnapConstraint
591  *
592  * Retrieves the offset set using clutter_snap_constraint_set_offset()
593  *
594  * Return value: the offset, in pixels
595  *
596  * Since: 1.6
597  */
598 gfloat
clutter_snap_constraint_get_offset(ClutterSnapConstraint * constraint)599 clutter_snap_constraint_get_offset (ClutterSnapConstraint *constraint)
600 {
601   g_return_val_if_fail (CLUTTER_IS_SNAP_CONSTRAINT (constraint), 0.0);
602 
603   return constraint->offset;
604 }
605