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