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