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