1 #include <stdio.h>
2 #include <stdlib.h>
3 
4 #include <gmodule.h>
5 #include <cogl/cogl.h>
6 
7 #include <clutter/clutter.h>
8 
9 /* layout actor, by Lucas Rocha */
10 
11 #define MY_TYPE_THING                (my_thing_get_type ())
12 #define MY_THING(obj)                (G_TYPE_CHECK_INSTANCE_CAST ((obj), MY_TYPE_THING, MyThing))
13 #define MY_IS_THING(obj)             (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MY_TYPE_THING))
14 #define MY_THING_CLASS(klass)        (G_TYPE_CHECK_CLASS_CAST ((klass),  MY_TYPE_THING, MyThingClass))
15 #define MY_IS_THING_CLASS(klass)     (G_TYPE_CHECK_CLASS_TYPE ((klass),  MY_TYPE_THING))
16 #define MY_THING_GET_CLASS(obj)      (G_TYPE_INSTANCE_GET_CLASS ((obj),  MY_TYPE_THING, MyThingClass))
17 
18 typedef struct _MyThing             MyThing;
19 typedef struct _MyThingPrivate      MyThingPrivate;
20 typedef struct _MyThingClass        MyThingClass;
21 
22 struct _MyThing
23 {
24   ClutterActor parent_instance;
25 
26   MyThingPrivate *priv;
27 };
28 
29 struct _MyThingClass
30 {
31   ClutterActorClass parent_class;
32 };
33 
34 enum
35 {
36   PROP_0,
37 
38   PROP_SPACING,
39   PROP_PADDING,
40   PROP_USE_TRANSFORMED_BOX
41 };
42 
43 G_DEFINE_TYPE (MyThing, my_thing, CLUTTER_TYPE_ACTOR)
44 
45 #define MY_THING_GET_PRIVATE(obj)    \
46 (G_TYPE_INSTANCE_GET_PRIVATE ((obj), MY_TYPE_THING, MyThingPrivate))
47 
48 struct _MyThingPrivate
49 {
50   gfloat  spacing;
51   gfloat  padding;
52 
53   guint   use_transformed_box : 1;
54 };
55 
56 static void
my_thing_set_property(GObject * gobject,guint prop_id,const GValue * value,GParamSpec * pspec)57 my_thing_set_property (GObject      *gobject,
58                        guint         prop_id,
59                        const GValue *value,
60                        GParamSpec   *pspec)
61 {
62   MyThingPrivate *priv = MY_THING (gobject)->priv;
63   gboolean needs_relayout = TRUE;
64 
65   switch (prop_id)
66     {
67     case PROP_SPACING:
68       priv->spacing = g_value_get_float (value);
69       break;
70 
71     case PROP_PADDING:
72       priv->padding = g_value_get_float (value);
73       break;
74 
75     case PROP_USE_TRANSFORMED_BOX:
76       priv->use_transformed_box = g_value_get_boolean (value);
77       break;
78 
79     default:
80       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
81       needs_relayout = FALSE;
82       break;
83     }
84 
85   /* setting spacing or padding queues a relayout
86      because they are supposed to change the internal
87      allocation of children */
88   if (needs_relayout)
89     clutter_actor_queue_relayout (CLUTTER_ACTOR (gobject));
90 }
91 
92 static void
my_thing_get_property(GObject * gobject,guint prop_id,GValue * value,GParamSpec * pspec)93 my_thing_get_property (GObject    *gobject,
94                        guint       prop_id,
95                        GValue     *value,
96                        GParamSpec *pspec)
97 {
98   MyThingPrivate *priv = MY_THING (gobject)->priv;
99 
100   switch (prop_id)
101     {
102     case PROP_SPACING:
103       g_value_set_float (value, priv->spacing);
104       break;
105 
106     case PROP_PADDING:
107       g_value_set_float (value, priv->padding);
108       break;
109 
110     case PROP_USE_TRANSFORMED_BOX:
111       g_value_set_boolean (value, priv->use_transformed_box);
112       break;
113 
114     default:
115       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
116       break;
117     }
118 }
119 
120 static void
my_thing_get_preferred_width(ClutterActor * self,gfloat for_height,gfloat * min_width_p,gfloat * natural_width_p)121 my_thing_get_preferred_width (ClutterActor *self,
122                               gfloat   for_height,
123                               gfloat  *min_width_p,
124                               gfloat  *natural_width_p)
125 {
126   ClutterActorIter iter;
127   ClutterActor *child;
128   gfloat min_left, min_right;
129   gfloat natural_left, natural_right;
130 
131   min_left = 0;
132   min_right = 0;
133   natural_left = 0;
134   natural_right = 0;
135 
136   clutter_actor_iter_init (&iter, self);
137   while (clutter_actor_iter_next (&iter, &child))
138     {
139       gfloat child_x, child_min, child_natural;
140 
141       child_x = clutter_actor_get_x (child);
142 
143       clutter_actor_get_preferred_size (child,
144                                         &child_min, NULL,
145                                         &child_natural, NULL);
146 
147       if (child == clutter_actor_get_first_child (self))
148         {
149           /* First child */
150           min_left = child_x;
151           natural_left = child_x;
152           min_right = min_left + child_min;
153           natural_right = natural_left + child_natural;
154         }
155       else
156         {
157           /* Union of extents with previous children */
158           if (child_x < min_left)
159             min_left = child_x;
160 
161           if (child_x < natural_left)
162             natural_left = child_x;
163 
164           if (child_x + child_min > min_right)
165             min_right = child_x + child_min;
166 
167           if (child_x + child_natural > natural_right)
168             natural_right = child_x + child_natural;
169         }
170     }
171 
172   if (min_left < 0)
173     min_left = 0;
174 
175   if (natural_left < 0)
176     natural_left = 0;
177 
178   if (min_right < 0)
179     min_right = 0;
180 
181   if (natural_right < 0)
182     natural_right = 0;
183 
184   g_assert (min_right >= min_left);
185   g_assert (natural_right >= natural_left);
186 
187   if (min_width_p)
188     *min_width_p = min_right - min_left;
189 
190   if (natural_width_p)
191     *natural_width_p = natural_right - min_left;
192 }
193 
194 static void
my_thing_get_preferred_height(ClutterActor * self,gfloat for_width,gfloat * min_height_p,gfloat * natural_height_p)195 my_thing_get_preferred_height (ClutterActor *self,
196                                gfloat   for_width,
197                                gfloat  *min_height_p,
198                                gfloat  *natural_height_p)
199 {
200   ClutterActorIter iter;
201   ClutterActor *child;
202   gfloat min_top, min_bottom;
203   gfloat natural_top, natural_bottom;
204 
205   min_top = 0;
206   min_bottom = 0;
207   natural_top = 0;
208   natural_bottom = 0;
209 
210   clutter_actor_iter_init (&iter, self);
211   while (clutter_actor_iter_next (&iter, &child))
212     {
213       gfloat child_y, child_min, child_natural;
214 
215       child_y = clutter_actor_get_y (child);
216 
217       clutter_actor_get_preferred_size (child,
218                                         NULL, &child_min,
219                                         NULL, &child_natural);
220 
221       if (child == clutter_actor_get_first_child (self))
222         {
223           /* First child */
224           min_top = child_y;
225           natural_top = child_y;
226           min_bottom = min_top + child_min;
227           natural_bottom = natural_top + child_natural;
228         }
229       else
230         {
231           /* Union of extents with previous children */
232           if (child_y < min_top)
233             min_top = child_y;
234 
235           if (child_y < natural_top)
236             natural_top = child_y;
237 
238           if (child_y + child_min > min_bottom)
239             min_bottom = child_y + child_min;
240 
241           if (child_y + child_natural > natural_bottom)
242             natural_bottom = child_y + child_natural;
243         }
244     }
245 
246   if (min_top < 0)
247     min_top = 0;
248 
249   if (natural_top < 0)
250     natural_top = 0;
251 
252   if (min_bottom < 0)
253     min_bottom = 0;
254 
255   if (natural_bottom < 0)
256     natural_bottom = 0;
257 
258   g_assert (min_bottom >= min_top);
259   g_assert (natural_bottom >= natural_top);
260 
261   if (min_height_p)
262     *min_height_p = min_bottom - min_top;
263 
264   if (natural_height_p)
265     *natural_height_p = natural_bottom - min_top;
266 }
267 
268 static void
my_thing_allocate(ClutterActor * self,const ClutterActorBox * box,ClutterAllocationFlags flags)269 my_thing_allocate (ClutterActor           *self,
270                    const ClutterActorBox  *box,
271                    ClutterAllocationFlags  flags)
272 {
273   MyThingPrivate *priv;
274   gfloat current_x, current_y, max_row_height;
275   ClutterActorIter iter;
276   ClutterActor *child;
277 
278   clutter_actor_set_allocation (self, box, flags);
279 
280   priv = MY_THING (self)->priv;
281 
282   current_x = priv->padding;
283   current_y = priv->padding;
284   max_row_height = 0;
285 
286   /* The allocation logic here is to horizontally place children
287    * side-by-side and reflow into a new row when we run out of
288    * space
289    */
290   clutter_actor_iter_init (&iter, self);
291   while (clutter_actor_iter_next (&iter, &child))
292     {
293       gfloat natural_width, natural_height;
294       ClutterActorBox child_box;
295 
296       clutter_actor_get_preferred_size (child,
297                                         NULL, NULL,
298                                         &natural_width,
299                                         &natural_height);
300 
301       /* if it fits in the current row, keep it there; otherwise
302        * reflow into another row
303        */
304       if (current_x + natural_width > box->x2 - box->x1 - priv->padding)
305         {
306           current_x = priv->padding;
307           current_y += max_row_height + priv->spacing;
308           max_row_height = 0;
309         }
310 
311       child_box.x1 = current_x;
312       child_box.y1 = current_y;
313       child_box.x2 = child_box.x1 + natural_width;
314       child_box.y2 = child_box.y1 + natural_height;
315 
316       clutter_actor_allocate (child, &child_box, flags);
317 
318       /* if we take into account the transformation of the children
319        * then we first check if it's transformed; then we get the
320        * onscreen coordinates of the two points of the bounding box
321        * of the actor (origin(x, y) and (origin + size)(x,y)) and
322        * we update the coordinates and area given to the next child
323        */
324       if (priv->use_transformed_box)
325         {
326           if (clutter_actor_is_scaled (child) ||
327               clutter_actor_is_rotated (child))
328             {
329               ClutterVertex v1 = { 0, }, v2 = { 0, };
330               ClutterActorBox transformed_box = { 0, };
331 
332               /* origin */
333               if (!(flags & CLUTTER_ABSOLUTE_ORIGIN_CHANGED))
334                 {
335                   v1.x = 0;
336                   v1.y = 0;
337                 }
338               else
339                 {
340                   v1.x = box->x1;
341                   v1.y = box->y1;
342                 }
343 
344               clutter_actor_apply_transform_to_point (child, &v1, &v2);
345               transformed_box.x1 = v2.x;
346               transformed_box.y1 = v2.y;
347 
348               /* size */
349               v1.x = natural_width;
350               v1.y = natural_height;
351               clutter_actor_apply_transform_to_point (child, &v1, &v2);
352               transformed_box.x2 = v2.x;
353               transformed_box.y2 = v2.y;
354 
355               natural_width = transformed_box.x2 - transformed_box.x1;
356               natural_height = transformed_box.y2 - transformed_box.y1;
357             }
358         }
359 
360       /* Record the maximum child height on current row to know
361        * what's the increment that should be used for the next
362        * row
363        */
364       if (natural_height > max_row_height)
365         max_row_height = natural_height;
366 
367       current_x += natural_width + priv->spacing;
368     }
369 }
370 
371 #define MIN_SIZE 24
372 #define MAX_SIZE 64
373 
374 static void
my_thing_class_init(MyThingClass * klass)375 my_thing_class_init (MyThingClass *klass)
376 {
377   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
378   ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
379 
380   gobject_class->set_property = my_thing_set_property;
381   gobject_class->get_property = my_thing_get_property;
382 
383   actor_class->get_preferred_width  = my_thing_get_preferred_width;
384   actor_class->get_preferred_height = my_thing_get_preferred_height;
385   actor_class->allocate = my_thing_allocate;
386 
387   g_object_class_install_property (gobject_class,
388                                    PROP_SPACING,
389                                    g_param_spec_float ("spacing",
390                                                        "Spacing",
391                                                        "Spacing of the thing",
392                                                        0, G_MAXFLOAT,
393                                                        0,
394                                                        G_PARAM_READWRITE));
395 
396   g_object_class_install_property (gobject_class,
397                                    PROP_PADDING,
398                                    g_param_spec_float ("padding",
399                                                        "Padding",
400                                                        "Padding around the thing",
401                                                        0, G_MAXFLOAT,
402                                                        0,
403                                                        G_PARAM_READWRITE));
404 
405   g_object_class_install_property (gobject_class,
406                                    PROP_USE_TRANSFORMED_BOX,
407                                    g_param_spec_boolean ("use-transformed-box",
408                                                          "Use Transformed Box",
409                                                          "Use transformed box when allocating",
410                                                          FALSE,
411                                                          G_PARAM_READWRITE));
412 
413   g_type_class_add_private (klass, sizeof (MyThingPrivate));
414 }
415 
416 static void
my_thing_init(MyThing * thing)417 my_thing_init (MyThing *thing)
418 {
419   thing->priv = MY_THING_GET_PRIVATE (thing);
420 }
421 
422 ClutterActor *
my_thing_new(gfloat padding,gfloat spacing)423 my_thing_new (gfloat padding,
424               gfloat spacing)
425 {
426   return g_object_new (MY_TYPE_THING,
427                        "padding", padding,
428                        "spacing", spacing,
429                        NULL);
430 }
431 
432 /* test code */
433 
434 static ClutterActor *box              = NULL;
435 static ClutterActor *icon             = NULL;
436 static ClutterTimeline *main_timeline = NULL;
437 
438 static void
toggle_property_value(ClutterActor * actor,const gchar * property_name)439 toggle_property_value (ClutterActor *actor,
440                        const gchar  *property_name)
441 {
442   gboolean value;
443 
444   g_object_get (actor, property_name, &value, NULL);
445 
446   value = !value;
447 
448   g_object_set (box, property_name, value, NULL);
449 }
450 
451 static void
increase_property_value(ClutterActor * actor,const char * property_name)452 increase_property_value (ClutterActor *actor,
453                          const char   *property_name)
454 {
455   gfloat value;
456 
457   g_object_get (actor, property_name, &value, NULL);
458 
459   value = value + 10.0;
460 
461   g_object_set (box, property_name, value, NULL);
462 }
463 
464 static void
decrease_property_value(ClutterActor * actor,const char * property_name)465 decrease_property_value (ClutterActor *actor,
466                          const char   *property_name)
467 {
468   gfloat value;
469 
470   g_object_get (actor, property_name, &value, NULL);
471 
472   value = MAX (0, value - 10.0);
473 
474   g_object_set (box, property_name, value, NULL);
475 }
476 
477 static ClutterActor *
create_item(void)478 create_item (void)
479 {
480   ClutterActor *clone = clutter_clone_new (icon);
481 
482   gint32 size = g_random_int_range (MIN_SIZE, MAX_SIZE);
483 
484   clutter_actor_set_size (clone, size, size);
485   clutter_actor_animate_with_timeline (clone, CLUTTER_EASE_OUT_CUBIC,
486                                        main_timeline,
487                                        "scale-x", 2.0,
488                                        "scale-y", 2.0,
489                                        "fixed::scale-gravity", CLUTTER_GRAVITY_CENTER,
490                                        NULL);
491 
492   return clone;
493 }
494 
495 static gboolean
keypress_cb(ClutterActor * actor,ClutterEvent * event,gpointer data)496 keypress_cb (ClutterActor *actor,
497 	     ClutterEvent *event,
498 	     gpointer      data)
499 {
500   switch (clutter_event_get_key_symbol (event))
501     {
502     case CLUTTER_KEY_q:
503       clutter_main_quit ();
504       break;
505 
506     case CLUTTER_KEY_a:
507       {
508         if (icon != NULL)
509           {
510             ClutterActor *clone = create_item ();
511 
512             /* Add one item to container */
513             clutter_actor_add_child (box, clone);
514           }
515         break;
516       }
517 
518     case CLUTTER_KEY_d:
519       {
520         ClutterActor *last_child;
521 
522         last_child = clutter_actor_get_last_child (box);
523         if (last_child != NULL)
524           {
525             /* Remove last item on container */
526             clutter_actor_remove_child (box, last_child);
527           }
528         break;
529       }
530 
531     case CLUTTER_KEY_w:
532       {
533         decrease_property_value (box, "padding");
534         break;
535       }
536 
537     case CLUTTER_KEY_e:
538       {
539         increase_property_value (box, "padding");
540         break;
541       }
542 
543     case CLUTTER_KEY_r:
544       {
545         decrease_property_value (box, "spacing");
546         break;
547       }
548 
549     case CLUTTER_KEY_s:
550       {
551         toggle_property_value (box, "use-transformed-box");
552         break;
553       }
554 
555     case CLUTTER_KEY_t:
556       {
557         increase_property_value (box, "spacing");
558         break;
559       }
560 
561     case CLUTTER_KEY_z:
562       {
563         if (clutter_timeline_is_playing (main_timeline))
564           clutter_timeline_pause (main_timeline);
565         else
566           clutter_timeline_start (main_timeline);
567 
568         break;
569       }
570 
571     default:
572       break;
573     }
574 
575   return FALSE;
576 }
577 
578 static void
relayout_on_frame(ClutterTimeline * timeline)579 relayout_on_frame (ClutterTimeline *timeline)
580 {
581   gboolean use_transformed_box;
582 
583   /* if we care about transformations updating the layout, we need to inform
584    * the layout that a transformation is happening; this is either done by
585    * attaching a notification on the transformation properties or by simply
586    * queuing a relayout on each frame of the timeline used to drive the
587    * behaviour. for simplicity's sake, we used the latter
588    */
589 
590   g_object_get (G_OBJECT (box),
591                 "use-transformed-box", &use_transformed_box,
592                 NULL);
593 
594   if (use_transformed_box)
595     clutter_actor_queue_relayout (box);
596 }
597 
598 G_MODULE_EXPORT int
test_layout_main(int argc,char * argv[])599 test_layout_main (int argc, char *argv[])
600 {
601   ClutterActor *stage, *instructions;
602   gint i, size;
603   GError *error = NULL;
604 
605   if (clutter_init (&argc, &argv) != CLUTTER_INIT_SUCCESS)
606     return 1;
607 
608   stage = clutter_stage_new ();
609   clutter_actor_set_size (stage, 800, 600);
610   clutter_stage_set_title (CLUTTER_STAGE (stage), "Layout");
611   g_signal_connect (stage, "destroy", G_CALLBACK (clutter_main_quit), NULL);
612 
613   main_timeline = clutter_timeline_new (2000);
614   clutter_timeline_set_repeat_count (main_timeline, -1);
615   clutter_timeline_set_auto_reverse (main_timeline, TRUE);
616   g_signal_connect (main_timeline, "new-frame",
617                     G_CALLBACK (relayout_on_frame),
618                     NULL);
619 
620 
621   box = my_thing_new (10, 10);
622 
623   clutter_actor_set_position (box, 20, 20);
624   clutter_actor_set_size (box, 350, -1);
625 
626   icon = clutter_texture_new_from_file (TESTS_DATADIR
627                                         G_DIR_SEPARATOR_S
628                                         "redhand.png",
629                                         &error);
630   if (error)
631     g_error ("Unable to load 'redhand.png': %s", error->message);
632 
633   size = g_random_int_range (MIN_SIZE, MAX_SIZE);
634   clutter_actor_set_size (icon, size, size);
635   clutter_actor_add_child (box, icon);
636   clutter_actor_animate_with_timeline (icon, CLUTTER_EASE_OUT_CUBIC,
637                                        main_timeline,
638                                        "scale-x", 2.0,
639                                        "scale-y", 2.0,
640                                        "fixed::scale-gravity", CLUTTER_GRAVITY_CENTER,
641                                        NULL);
642 
643   for (i = 1; i < 33; i++)
644     {
645       ClutterActor *clone = create_item ();
646 
647       clutter_actor_add_child (box, clone);
648     }
649 
650   clutter_actor_add_child (stage, box);
651 
652   instructions = clutter_text_new_with_text (NULL,
653                                               "<b>Instructions:</b>\n"
654                                               "a - add a new item\n"
655                                               "d - remove last item\n"
656                                               "z - start/pause behaviour\n"
657                                               "w - decrease padding\n"
658                                               "e - increase padding\n"
659                                               "r - decrease spacing\n"
660                                               "t - increase spacing\n"
661                                               "s - use transformed box\n"
662                                               "q - quit");
663 
664   clutter_text_set_use_markup (CLUTTER_TEXT (instructions), TRUE);
665   clutter_actor_set_position (instructions, 450, 10);
666   clutter_actor_add_child (stage, instructions);
667 
668   g_signal_connect (stage, "key-release-event",
669 		    G_CALLBACK (keypress_cb),
670 		    NULL);
671 
672   clutter_timeline_stop (main_timeline);
673 
674   clutter_actor_show (stage);
675 
676   clutter_main ();
677 
678   g_object_unref (main_timeline);
679 
680   return EXIT_SUCCESS;
681 }
682 
683 G_MODULE_EXPORT const char *
test_layout_describe(void)684 test_layout_describe (void)
685 {
686   return "Container implementing a layout policy.";
687 }
688