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