1 /*
2 * Clutter.
3 *
4 * An OpenGL based 'interactive canvas' library.
5 *
6 * Authored By Matthew Allum <mallum@openedhand.com>
7 *
8 * Copyright (C) 2006 OpenedHand
9 *
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2 of the License, or (at your option) any later version.
14 *
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
19 *
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
22 */
23
24 /**
25 * SECTION:clutter-group
26 * @short_description: A fixed layout container
27 *
28 * A #ClutterGroup is an Actor which contains multiple child actors positioned
29 * relative to the #ClutterGroup position. Other operations such as scaling,
30 * rotating and clipping of the group will apply to the child actors.
31 *
32 * A #ClutterGroup's size is defined by the size and position of its children;
33 * it will be the smallest non-negative size that covers the right and bottom
34 * edges of all of its children.
35 *
36 * Setting the size on a Group using #ClutterActor methods like
37 * clutter_actor_set_size() will override the natural size of the Group,
38 * however this will not affect the size of the children and they may still
39 * be painted outside of the allocation of the group. One way to constrain
40 * the visible area of a #ClutterGroup to a specified allocation is to
41 * explicitly set the size of the #ClutterGroup and then use the
42 * #ClutterActor:clip-to-allocation property.
43 *
44 * #ClutterGroup as a concrete class has been superceded by #ClutterActor
45 * since Clutter 1.10. The type itself is not deprecated as it is used by
46 * #ClutterStage. You should instantiate #ClutterActor and use its API to
47 * manage child actors.
48 */
49
50 #ifdef HAVE_CONFIG_H
51 #include "config.h"
52 #endif
53
54 #include <stdarg.h>
55
56 #define CLUTTER_DISABLE_DEPRECATION_WARNINGS
57 #include "clutter-group.h"
58
59 #include "clutter-actor.h"
60 #include "clutter-actor-private.h"
61 #include "clutter-container.h"
62 #include "clutter-fixed-layout.h"
63 #include "clutter-main.h"
64 #include "clutter-debug.h"
65 #include "clutter-enum-types.h"
66 #include "clutter-marshal.h"
67 #include "clutter-private.h"
68
69 #include "cogl/cogl.h"
70
71 struct _ClutterGroupPrivate
72 {
73 GList *children;
74
75 ClutterLayoutManager *layout;
76 };
77
78 static void clutter_container_iface_init (ClutterContainerIface *iface);
79
80 G_DEFINE_TYPE_WITH_CODE (ClutterGroup, clutter_group, CLUTTER_TYPE_ACTOR,
81 G_ADD_PRIVATE (ClutterGroup)
82 G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTAINER,
83 clutter_container_iface_init));
84
85 static gint
sort_by_depth(gconstpointer a,gconstpointer b)86 sort_by_depth (gconstpointer a,
87 gconstpointer b)
88 {
89 gfloat depth_a = clutter_actor_get_depth (CLUTTER_ACTOR(a));
90 gfloat depth_b = clutter_actor_get_depth (CLUTTER_ACTOR(b));
91
92 if (depth_a < depth_b)
93 return -1;
94
95 if (depth_a > depth_b)
96 return 1;
97
98 return 0;
99 }
100
101 static void
clutter_group_real_add(ClutterContainer * container,ClutterActor * actor)102 clutter_group_real_add (ClutterContainer *container,
103 ClutterActor *actor)
104 {
105 ClutterGroupPrivate *priv = CLUTTER_GROUP (container)->priv;
106
107 g_object_ref (actor);
108
109 priv->children = g_list_append (priv->children, actor);
110 clutter_actor_set_parent (actor, CLUTTER_ACTOR (container));
111
112 clutter_actor_queue_relayout (CLUTTER_ACTOR (container));
113
114 g_signal_emit_by_name (container, "actor-added", actor);
115
116 clutter_container_sort_depth_order (container);
117
118 g_object_unref (actor);
119 }
120
121 static void
clutter_group_real_actor_added(ClutterContainer * container,ClutterActor * actor)122 clutter_group_real_actor_added (ClutterContainer *container,
123 ClutterActor *actor)
124 {
125 ClutterGroupPrivate *priv = CLUTTER_GROUP (container)->priv;
126
127 /* XXX - children added using clutter_actor_add_child() will
128 * cause actor-added to be emitted without going through the
129 * add() virtual function.
130 *
131 * if we get an actor-added for a child that is not in our
132 * list of children already, then we go in compatibility
133 * mode.
134 */
135 if (g_list_find (priv->children, actor) != NULL)
136 return;
137
138 priv->children = g_list_append (priv->children, actor);
139 clutter_container_sort_depth_order (container);
140 }
141
142 static void
clutter_group_real_remove(ClutterContainer * container,ClutterActor * actor)143 clutter_group_real_remove (ClutterContainer *container,
144 ClutterActor *actor)
145 {
146 ClutterGroupPrivate *priv = CLUTTER_GROUP (container)->priv;
147
148 g_object_ref (actor);
149
150 priv->children = g_list_remove (priv->children, actor);
151 clutter_actor_unparent (actor);
152
153 clutter_actor_queue_relayout (CLUTTER_ACTOR (container));
154
155 g_signal_emit_by_name (container, "actor-removed", actor);
156
157 clutter_actor_queue_redraw (CLUTTER_ACTOR (container));
158
159 g_object_unref (actor);
160 }
161
162 static void
clutter_group_real_actor_removed(ClutterContainer * container,ClutterActor * actor)163 clutter_group_real_actor_removed (ClutterContainer *container,
164 ClutterActor *actor)
165 {
166 ClutterGroupPrivate *priv = CLUTTER_GROUP (container)->priv;
167
168 /* XXX - same compatibility mode of the ::actor-added implementation */
169 if (g_list_find (priv->children, actor) == NULL)
170 return;
171
172 priv->children = g_list_remove (priv->children, actor);
173 }
174
175 static void
clutter_group_real_foreach(ClutterContainer * container,ClutterCallback callback,gpointer user_data)176 clutter_group_real_foreach (ClutterContainer *container,
177 ClutterCallback callback,
178 gpointer user_data)
179 {
180 ClutterGroupPrivate *priv = CLUTTER_GROUP (container)->priv;
181
182 /* Using g_list_foreach instead of iterating the list manually
183 because it has better protection against the current node being
184 removed. This will happen for example if someone calls
185 clutter_container_foreach(container, clutter_actor_destroy) */
186 g_list_foreach (priv->children, (GFunc) callback, user_data);
187 }
188
189 static void
clutter_group_real_raise(ClutterContainer * container,ClutterActor * actor,ClutterActor * sibling)190 clutter_group_real_raise (ClutterContainer *container,
191 ClutterActor *actor,
192 ClutterActor *sibling)
193 {
194 ClutterGroupPrivate *priv = CLUTTER_GROUP (container)->priv;
195
196 priv->children = g_list_remove (priv->children, actor);
197
198 /* Raise at the top */
199 if (!sibling)
200 {
201 GList *last_item;
202
203 last_item = g_list_last (priv->children);
204
205 if (last_item)
206 sibling = last_item->data;
207
208 priv->children = g_list_append (priv->children, actor);
209 }
210 else
211 {
212 gint index_ = g_list_index (priv->children, sibling) + 1;
213
214 priv->children = g_list_insert (priv->children, actor, index_);
215 }
216
217 /* set Z ordering a value below, this will then call sort
218 * as values are equal ordering shouldn't change but Z
219 * values will be correct.
220 *
221 * FIXME: get rid of this crap; this is so utterly broken and wrong on
222 * so many levels it's not even funny. sadly, we get to keep this until
223 * we can break API and remove Group for good.
224 */
225 if (sibling &&
226 clutter_actor_get_depth (sibling) != clutter_actor_get_depth (actor))
227 {
228 clutter_actor_set_depth (actor, clutter_actor_get_depth (sibling));
229 }
230
231 clutter_actor_queue_redraw (CLUTTER_ACTOR (container));
232 }
233
234 static void
clutter_group_real_lower(ClutterContainer * container,ClutterActor * actor,ClutterActor * sibling)235 clutter_group_real_lower (ClutterContainer *container,
236 ClutterActor *actor,
237 ClutterActor *sibling)
238 {
239 ClutterGroup *self = CLUTTER_GROUP (container);
240 ClutterGroupPrivate *priv = self->priv;
241
242 priv->children = g_list_remove (priv->children, actor);
243
244 /* Push to bottom */
245 if (!sibling)
246 {
247 GList *last_item;
248
249 last_item = g_list_first (priv->children);
250
251 if (last_item)
252 sibling = last_item->data;
253
254 priv->children = g_list_prepend (priv->children, actor);
255 }
256 else
257 {
258 gint index_ = g_list_index (priv->children, sibling);
259
260 priv->children = g_list_insert (priv->children, actor, index_);
261 }
262
263 /* See comment in group_raise for this */
264 if (sibling &&
265 clutter_actor_get_depth (sibling) != clutter_actor_get_depth (actor))
266 {
267 clutter_actor_set_depth (actor, clutter_actor_get_depth (sibling));
268 }
269
270 clutter_actor_queue_redraw (CLUTTER_ACTOR (container));
271 }
272
273 static void
clutter_group_real_sort_depth_order(ClutterContainer * container)274 clutter_group_real_sort_depth_order (ClutterContainer *container)
275 {
276 ClutterGroupPrivate *priv = CLUTTER_GROUP (container)->priv;
277
278 priv->children = g_list_sort (priv->children, sort_by_depth);
279
280 clutter_actor_queue_redraw (CLUTTER_ACTOR (container));
281 }
282
283 static void
clutter_container_iface_init(ClutterContainerIface * iface)284 clutter_container_iface_init (ClutterContainerIface *iface)
285 {
286 iface->add = clutter_group_real_add;
287 iface->actor_added = clutter_group_real_actor_added;
288 iface->remove = clutter_group_real_remove;
289 iface->actor_removed = clutter_group_real_actor_removed;
290 iface->foreach = clutter_group_real_foreach;
291 iface->raise = clutter_group_real_raise;
292 iface->lower = clutter_group_real_lower;
293 iface->sort_depth_order = clutter_group_real_sort_depth_order;
294 }
295
296 static void
clutter_group_real_paint(ClutterActor * actor)297 clutter_group_real_paint (ClutterActor *actor)
298 {
299 ClutterGroupPrivate *priv = CLUTTER_GROUP (actor)->priv;
300
301 CLUTTER_NOTE (PAINT, "ClutterGroup paint enter '%s'",
302 _clutter_actor_get_debug_name (actor));
303
304 g_list_foreach (priv->children, (GFunc) clutter_actor_paint, NULL);
305
306 CLUTTER_NOTE (PAINT, "ClutterGroup paint leave '%s'",
307 _clutter_actor_get_debug_name (actor));
308 }
309
310 static void
clutter_group_real_pick(ClutterActor * actor,const ClutterColor * pick)311 clutter_group_real_pick (ClutterActor *actor,
312 const ClutterColor *pick)
313 {
314 ClutterGroupPrivate *priv = CLUTTER_GROUP (actor)->priv;
315
316 /* Chain up so we get a bounding box pained (if we are reactive) */
317 CLUTTER_ACTOR_CLASS (clutter_group_parent_class)->pick (actor, pick);
318
319 g_list_foreach (priv->children, (GFunc) clutter_actor_paint, NULL);
320 }
321
322 static void
clutter_group_real_get_preferred_width(ClutterActor * actor,gfloat for_height,gfloat * min_width,gfloat * natural_width)323 clutter_group_real_get_preferred_width (ClutterActor *actor,
324 gfloat for_height,
325 gfloat *min_width,
326 gfloat *natural_width)
327 {
328 ClutterGroupPrivate *priv = CLUTTER_GROUP (actor)->priv;
329
330 clutter_layout_manager_get_preferred_width (priv->layout,
331 CLUTTER_CONTAINER (actor),
332 for_height,
333 min_width, natural_width);
334 }
335
336 static void
clutter_group_real_get_preferred_height(ClutterActor * actor,gfloat for_width,gfloat * min_height,gfloat * natural_height)337 clutter_group_real_get_preferred_height (ClutterActor *actor,
338 gfloat for_width,
339 gfloat *min_height,
340 gfloat *natural_height)
341 {
342 ClutterGroupPrivate *priv = CLUTTER_GROUP (actor)->priv;
343
344 clutter_layout_manager_get_preferred_height (priv->layout,
345 CLUTTER_CONTAINER (actor),
346 for_width,
347 min_height, natural_height);
348 }
349
350 static void
clutter_group_real_allocate(ClutterActor * actor,const ClutterActorBox * allocation,ClutterAllocationFlags flags)351 clutter_group_real_allocate (ClutterActor *actor,
352 const ClutterActorBox *allocation,
353 ClutterAllocationFlags flags)
354 {
355 ClutterGroupPrivate *priv = CLUTTER_GROUP (actor)->priv;
356 ClutterActorClass *klass;
357
358 klass = CLUTTER_ACTOR_CLASS (clutter_group_parent_class);
359 klass->allocate (actor, allocation, flags);
360
361 if (priv->children == NULL)
362 return;
363
364 clutter_layout_manager_allocate (priv->layout,
365 CLUTTER_CONTAINER (actor),
366 allocation, flags);
367 }
368
369 static void
clutter_group_dispose(GObject * object)370 clutter_group_dispose (GObject *object)
371 {
372 ClutterGroup *self = CLUTTER_GROUP (object);
373 ClutterGroupPrivate *priv = self->priv;
374
375 /* Note: we are careful to consider that destroying children could
376 * have the side-effect of destroying other children so
377 * priv->children may be modified during clutter_actor_destroy. */
378 while (priv->children != NULL)
379 {
380 ClutterActor *child = priv->children->data;
381 priv->children = g_list_delete_link (priv->children, priv->children);
382 clutter_actor_destroy (child);
383 }
384
385 if (priv->layout)
386 {
387 clutter_layout_manager_set_container (priv->layout, NULL);
388 g_object_unref (priv->layout);
389 priv->layout = NULL;
390 }
391
392 G_OBJECT_CLASS (clutter_group_parent_class)->dispose (object);
393 }
394
395 static void
clutter_group_real_show_all(ClutterActor * actor)396 clutter_group_real_show_all (ClutterActor *actor)
397 {
398 clutter_container_foreach (CLUTTER_CONTAINER (actor),
399 CLUTTER_CALLBACK (clutter_actor_show),
400 NULL);
401 clutter_actor_show (actor);
402 }
403
404 static void
clutter_group_real_hide_all(ClutterActor * actor)405 clutter_group_real_hide_all (ClutterActor *actor)
406 {
407 clutter_actor_hide (actor);
408 clutter_container_foreach (CLUTTER_CONTAINER (actor),
409 CLUTTER_CALLBACK (clutter_actor_hide),
410 NULL);
411 }
412
413 static gboolean
clutter_group_real_get_paint_volume(ClutterActor * actor,ClutterPaintVolume * volume)414 clutter_group_real_get_paint_volume (ClutterActor *actor,
415 ClutterPaintVolume *volume)
416 {
417 ClutterGroupPrivate *priv = CLUTTER_GROUP (actor)->priv;
418 GList *l;
419
420 if (priv->children == NULL)
421 return TRUE;
422
423 for (l = priv->children; l != NULL; l = l->next)
424 {
425 ClutterActor *child = l->data;
426 const ClutterPaintVolume *child_volume;
427
428 /* This gets the paint volume of the child transformed into the
429 * group's coordinate space... */
430 child_volume = clutter_actor_get_transformed_paint_volume (child, actor);
431 if (!child_volume)
432 return FALSE;
433
434 clutter_paint_volume_union (volume, child_volume);
435 }
436
437 return TRUE;
438 }
439
440 static void
clutter_group_class_init(ClutterGroupClass * klass)441 clutter_group_class_init (ClutterGroupClass *klass)
442 {
443 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
444 ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
445
446 actor_class->get_preferred_width = clutter_group_real_get_preferred_width;
447 actor_class->get_preferred_height = clutter_group_real_get_preferred_height;
448 actor_class->allocate = clutter_group_real_allocate;
449 actor_class->paint = clutter_group_real_paint;
450 actor_class->pick = clutter_group_real_pick;
451 actor_class->show_all = clutter_group_real_show_all;
452 actor_class->hide_all = clutter_group_real_hide_all;
453 actor_class->get_paint_volume = clutter_group_real_get_paint_volume;
454
455 gobject_class->dispose = clutter_group_dispose;
456 }
457
458 static void
clutter_group_init(ClutterGroup * self)459 clutter_group_init (ClutterGroup *self)
460 {
461 ClutterActor *actor = CLUTTER_ACTOR (self);
462
463 self->priv = clutter_group_get_instance_private (self);
464
465 /* turn on some optimization
466 *
467 * XXX - these so-called "optimizations" are insane and should have never
468 * been used. they introduce some weird behaviour that breaks invariants
469 * and have to be explicitly worked around.
470 *
471 * this flag was set by the ClutterFixedLayout, but since that layout
472 * manager is now the default for ClutterActor, we set the flag explicitly
473 * here, to avoid breaking perfectly working actors overriding the
474 * allocate() virtual function.
475 *
476 * also, we keep this flag here so that it can die once we get rid of
477 * ClutterGroup.
478 */
479 clutter_actor_set_flags (actor, CLUTTER_ACTOR_NO_LAYOUT);
480
481 self->priv->layout = clutter_fixed_layout_new ();
482 g_object_ref_sink (self->priv->layout);
483
484 clutter_actor_set_layout_manager (actor, self->priv->layout);
485 }
486
487 /**
488 * clutter_group_new:
489 *
490 * Create a new #ClutterGroup.
491 *
492 * Return value: the newly created #ClutterGroup actor
493 *
494 * Deprecated: 1.10: Use clutter_actor_new() instead.
495 */
496 ClutterActor *
clutter_group_new(void)497 clutter_group_new (void)
498 {
499 return g_object_new (CLUTTER_TYPE_GROUP, NULL);
500 }
501
502 /**
503 * clutter_group_remove_all:
504 * @self: A #ClutterGroup
505 *
506 * Removes all children actors from the #ClutterGroup.
507 *
508 * Deprecated: 1.10: Use clutter_actor_remove_all_children() instead.
509 */
510 void
clutter_group_remove_all(ClutterGroup * self)511 clutter_group_remove_all (ClutterGroup *self)
512 {
513 g_return_if_fail (CLUTTER_IS_GROUP (self));
514
515 clutter_actor_remove_all_children (CLUTTER_ACTOR (self));
516 }
517
518 /**
519 * clutter_group_get_n_children:
520 * @self: A #ClutterGroup
521 *
522 * Gets the number of actors held in the group.
523 *
524 * Return value: The number of child actors held in the group.
525 *
526 * Since: 0.2
527 *
528 * Deprecated: 1.10: Use clutter_actor_get_n_children() instead.
529 */
530 gint
clutter_group_get_n_children(ClutterGroup * self)531 clutter_group_get_n_children (ClutterGroup *self)
532 {
533 g_return_val_if_fail (CLUTTER_IS_GROUP (self), 0);
534
535 return clutter_actor_get_n_children (CLUTTER_ACTOR (self));
536 }
537
538 /**
539 * clutter_group_get_nth_child:
540 * @self: A #ClutterGroup
541 * @index_: the position of the requested actor.
542 *
543 * Gets a groups child held at @index_ in stack.
544 *
545 * Return value: (transfer none): A Clutter actor, or %NULL if
546 * @index_ is invalid.
547 *
548 * Since: 0.2
549 *
550 * Deprecated: 1.10: Use clutter_actor_get_child_at_index() instead.
551 */
552 ClutterActor *
clutter_group_get_nth_child(ClutterGroup * self,gint index_)553 clutter_group_get_nth_child (ClutterGroup *self,
554 gint index_)
555 {
556 ClutterActor *actor;
557
558 g_return_val_if_fail (CLUTTER_IS_GROUP (self), NULL);
559
560 actor = CLUTTER_ACTOR (self);
561 g_return_val_if_fail (index_ <= clutter_actor_get_n_children (actor), NULL);
562
563 return clutter_actor_get_child_at_index (actor, index_);
564 }
565