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