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