1 #include <math.h>
2 #include <stdlib.h>
3 #include <clutter/clutter.h>
4 
5 typedef struct _MultiLayout             MultiLayout;
6 typedef struct _MultiLayoutClass        MultiLayoutClass;
7 
8 typedef enum {
9   MULTI_LAYOUT_GRID,
10   MULTI_LAYOUT_CIRCLE
11 } MultiLayoutState;
12 
13 struct _MultiLayout
14 {
15   ClutterLayoutManager parent_instance;
16 
17   /* the state of the layout */
18   MultiLayoutState state;
19 
20   /* spacing between children */
21   float spacing;
22 
23   /* cell size */
24   float cell_width;
25   float cell_height;
26 };
27 
28 struct _MultiLayoutClass
29 {
30   ClutterLayoutManagerClass parent_class;
31 };
32 
33 GType multi_layout_get_type (void);
34 
35 ClutterLayoutManager *  multi_layout_new                (void);
36 void                    multi_layout_set_state          (MultiLayout      *layout,
37                                                          MultiLayoutState  state);
38 MultiLayoutState        multi_layout_get_state          (MultiLayout      *layout);
39 void                    multi_layout_set_spacing        (MultiLayout      *layout,
40                                                          float             spacing);
41 
G_DEFINE_TYPE(MultiLayout,multi_layout,CLUTTER_TYPE_LAYOUT_MANAGER)42 G_DEFINE_TYPE (MultiLayout, multi_layout, CLUTTER_TYPE_LAYOUT_MANAGER)
43 
44 static void
45 multi_layout_get_preferred_width (ClutterLayoutManager *manager,
46                                   ClutterContainer     *container,
47                                   float                 for_height,
48                                   float                *min_width_p,
49                                   float                *nat_width_p)
50 {
51   MultiLayout *self = (MultiLayout *) manager;
52   float minimum, natural;
53   float max_natural_width;
54   ClutterActorIter iter;
55   ClutterActor *child;
56   int n_children;
57 
58   minimum = natural = 0.f;
59   max_natural_width = 0.f;
60   n_children = 0;
61 
62   clutter_actor_iter_init (&iter, CLUTTER_ACTOR (container));
63   while (clutter_actor_iter_next (&iter, &child))
64     {
65       float child_minimum, child_natural;
66 
67       if (!clutter_actor_is_visible (child))
68         continue;
69 
70       clutter_actor_get_preferred_width (child, -1.f,
71                                          &child_minimum,
72                                          &child_natural);
73 
74       max_natural_width = MAX (max_natural_width, child_natural);
75 
76       if (self->state == MULTI_LAYOUT_GRID)
77         {
78           minimum += child_minimum;
79           natural += child_natural;
80         }
81       else if (self->state == MULTI_LAYOUT_CIRCLE)
82         {
83           minimum = MAX (minimum, child_minimum);
84           natural = MAX (natural, child_natural);
85         }
86 
87       n_children += 1;
88     }
89 
90   self->cell_width = max_natural_width;
91 
92   minimum += (self->spacing * (n_children - 1));
93   natural += (self->spacing * (n_children - 1));
94 
95   if (min_width_p != NULL)
96     *min_width_p = minimum;
97 
98   if (nat_width_p != NULL)
99     *nat_width_p = natural;
100 }
101 
102 static void
multi_layout_get_preferred_height(ClutterLayoutManager * manager,ClutterContainer * container,float for_width,float * min_height_p,float * nat_height_p)103 multi_layout_get_preferred_height (ClutterLayoutManager *manager,
104                                    ClutterContainer     *container,
105                                    float                 for_width,
106                                    float                *min_height_p,
107                                    float                *nat_height_p)
108 {
109   MultiLayout *self = (MultiLayout *) manager;
110   float minimum, natural;
111   ClutterActorIter iter;
112   ClutterActor *child;
113   int n_children;
114 
115   minimum = natural = self->spacing * 2.f;
116   n_children = 0;
117 
118   clutter_actor_iter_init (&iter, CLUTTER_ACTOR (container));
119   while (clutter_actor_iter_next (&iter, &child))
120     {
121       float child_minimum, child_natural;
122 
123       if (!clutter_actor_is_visible (child))
124         continue;
125 
126       clutter_actor_get_preferred_height (child, -1.f,
127                                           &child_minimum,
128                                           &child_natural);
129 
130       minimum = MAX (minimum, child_minimum);
131       natural = MAX (natural, child_natural);
132 
133       n_children += 1;
134     }
135 
136   self->cell_height = natural;
137 
138   minimum += (self->spacing * (n_children - 1));
139   natural += (self->spacing * (n_children - 1));
140 
141   if (min_height_p != NULL)
142     *min_height_p = minimum;
143 
144   if (nat_height_p != NULL)
145     *nat_height_p = natural;
146 }
147 
148 static int
get_items_per_row(MultiLayout * self,float for_width)149 get_items_per_row (MultiLayout *self,
150                    float        for_width)
151 {
152   int n_columns;
153 
154   if (for_width < 0)
155     return 1;
156 
157   if (self->cell_width <= 0)
158     return 1;
159 
160   n_columns = (int) ((for_width + self->spacing) / (self->cell_width + self->spacing));
161 
162   return MAX (n_columns, 1);
163 }
164 
165 static int
get_visible_children(ClutterActor * actor)166 get_visible_children (ClutterActor *actor)
167 {
168   ClutterActorIter iter;
169   ClutterActor *child;
170   int n_visible_children = 0;
171 
172   clutter_actor_iter_init (&iter, actor);
173   while (clutter_actor_iter_next (&iter, &child))
174     {
175       if (clutter_actor_is_visible (child))
176         n_visible_children += 1;
177     }
178 
179   return n_visible_children;
180 }
181 
182 static void
multi_layout_allocate(ClutterLayoutManager * manager,ClutterContainer * container,const ClutterActorBox * allocation,ClutterAllocationFlags flags)183 multi_layout_allocate (ClutterLayoutManager   *manager,
184                        ClutterContainer       *container,
185                        const ClutterActorBox  *allocation,
186                        ClutterAllocationFlags  flags)
187 {
188   MultiLayout *self = (MultiLayout *) manager;
189   float avail_width, avail_height;
190   float x_offset, y_offset;
191   ClutterActorIter iter;
192   ClutterActor *child;
193   float item_x = 0.f, item_y = 0.f;
194   int n_items, n_items_per_row = 0, item_index;
195   ClutterPoint center = CLUTTER_POINT_INIT_ZERO;
196   double radius = 0, theta = 0;
197 
198   n_items = get_visible_children (CLUTTER_ACTOR (container));
199   if (n_items == 0)
200     return;
201 
202   clutter_actor_box_get_origin (allocation, &x_offset, &y_offset);
203   clutter_actor_box_get_size (allocation, &avail_width, &avail_height);
204 
205   /* ensure we have an updated value of cell_width and cell_height */
206   multi_layout_get_preferred_width (manager, container, avail_width, NULL, NULL);
207   multi_layout_get_preferred_height (manager, container, avail_height, NULL, NULL);
208 
209   item_index = 0;
210 
211   if (self->state == MULTI_LAYOUT_GRID)
212     {
213       n_items_per_row = get_items_per_row (self, avail_width);
214       item_x = x_offset;
215       item_y = y_offset;
216     }
217   else if (self->state == MULTI_LAYOUT_CIRCLE)
218     {
219       center.x = allocation->x2 / 2.f;
220       center.y = allocation->y2 / 2.f;
221       radius = MIN ((avail_width - self->cell_width) / 2.0,
222                     (avail_height - self->cell_height) / 2.0);
223     }
224 
225   clutter_actor_iter_init (&iter, CLUTTER_ACTOR (container));
226   while (clutter_actor_iter_next (&iter, &child))
227     {
228       ClutterActorBox child_allocation = CLUTTER_ACTOR_BOX_INIT_ZERO;
229 
230       if (!clutter_actor_is_visible (child))
231         continue;
232 
233       if (self->state == MULTI_LAYOUT_GRID)
234         {
235           if (item_index == n_items_per_row)
236             {
237               item_index = 0;
238               item_x = x_offset;
239               item_y += self->cell_height + self->spacing;
240             }
241 
242           child_allocation.x1 = item_x;
243           child_allocation.y1 = item_y;
244           child_allocation.x2 = child_allocation.x1 + self->cell_width;
245           child_allocation.y2 = child_allocation.y1 + self->cell_height;
246 
247           item_x += self->cell_width + self->spacing;
248         }
249       else if (self->state == MULTI_LAYOUT_CIRCLE)
250         {
251           theta = 2.0 * G_PI / n_items * item_index;
252           child_allocation.x1 = center.x + radius * sinf (theta) - (self->cell_width / 2.f);
253           child_allocation.y1 = center.y + radius * -cosf (theta) - (self->cell_height / 2.f);
254           child_allocation.x2 = child_allocation.x1 + self->cell_width;
255           child_allocation.y2 = child_allocation.y1 + self->cell_height;
256         }
257 
258       clutter_actor_allocate (child, &child_allocation, flags);
259 
260       item_index += 1;
261     }
262 }
263 
264 static void
multi_layout_class_init(MultiLayoutClass * klass)265 multi_layout_class_init (MultiLayoutClass *klass)
266 {
267   ClutterLayoutManagerClass *manager_class = CLUTTER_LAYOUT_MANAGER_CLASS (klass);
268 
269   manager_class->get_preferred_width = multi_layout_get_preferred_width;
270   manager_class->get_preferred_height = multi_layout_get_preferred_height;
271   manager_class->allocate = multi_layout_allocate;
272 }
273 
274 static void
multi_layout_init(MultiLayout * self)275 multi_layout_init (MultiLayout *self)
276 {
277   self->state = MULTI_LAYOUT_GRID;
278 
279   self->cell_width = -1.f;
280   self->cell_height = -1.f;
281 
282   self->spacing = 0.f;
283 }
284 
285 ClutterLayoutManager *
multi_layout_new(void)286 multi_layout_new (void)
287 {
288   return g_object_new (multi_layout_get_type (), NULL);
289 }
290 
291 void
multi_layout_set_state(MultiLayout * self,MultiLayoutState state)292 multi_layout_set_state (MultiLayout *self,
293                         MultiLayoutState  state)
294 {
295   if (self->state == state)
296     return;
297 
298   self->state = state;
299 
300   clutter_layout_manager_layout_changed (CLUTTER_LAYOUT_MANAGER (self));
301 }
302 
303 MultiLayoutState
multi_layout_get_state(MultiLayout * self)304 multi_layout_get_state (MultiLayout *self)
305 {
306   return self->state;
307 }
308 
309 void
multi_layout_set_spacing(MultiLayout * self,float spacing)310 multi_layout_set_spacing (MultiLayout *self,
311                           float spacing)
312 {
313   self->spacing = spacing;
314 
315   clutter_layout_manager_layout_changed (CLUTTER_LAYOUT_MANAGER (self));
316 }
317 
318 #define N_RECTS         16
319 #define RECT_SIZE       64.0
320 #define N_ROWS          4
321 #define PADDING         12.0
322 #define BOX_SIZE        (RECT_SIZE * (N_RECTS / N_ROWS) + PADDING * (N_RECTS / N_ROWS - 1))
323 
324 static gboolean
on_enter(ClutterActor * rect,ClutterEvent * event)325 on_enter (ClutterActor *rect,
326           ClutterEvent *event)
327 {
328   clutter_actor_set_scale (rect, 1.2, 1.2);
329 
330   return CLUTTER_EVENT_STOP;
331 }
332 
333 static gboolean
on_leave(ClutterActor * rect,ClutterEvent * event)334 on_leave (ClutterActor *rect,
335           ClutterEvent *event)
336 {
337   clutter_actor_set_scale (rect, 1.0, 1.0);
338 
339   return CLUTTER_EVENT_STOP;
340 }
341 
342 static gboolean
on_key_press(ClutterActor * stage,ClutterEvent * event,ClutterActor * box)343 on_key_press (ClutterActor *stage,
344               ClutterEvent *event,
345               ClutterActor *box)
346 {
347   guint keysym = clutter_event_get_key_symbol (event);
348   MultiLayout *layout = (MultiLayout *) clutter_actor_get_layout_manager (box);
349 
350 
351   switch (keysym)
352     {
353     case CLUTTER_KEY_q:
354       clutter_main_quit ();
355       break;
356 
357     case CLUTTER_KEY_t:
358       {
359         MultiLayoutState state = multi_layout_get_state (layout);
360 
361         if (state == MULTI_LAYOUT_GRID)
362           multi_layout_set_state (layout, MULTI_LAYOUT_CIRCLE);
363 
364         if (state == MULTI_LAYOUT_CIRCLE)
365           multi_layout_set_state (layout, MULTI_LAYOUT_GRID);
366       }
367       break;
368 
369     default:
370       break;
371     }
372 
373   return CLUTTER_EVENT_STOP;
374 }
375 
376 int
main(int argc,char * argv[])377 main (int argc, char *argv[])
378 {
379   ClutterActor *stage, *box, *label;
380   ClutterLayoutManager *manager;
381   ClutterMargin margin;
382   ClutterTransition *transition;
383   int i;
384 
385   if (clutter_init (&argc, &argv) != CLUTTER_INIT_SUCCESS)
386     return EXIT_FAILURE;
387 
388   stage = clutter_stage_new ();
389   clutter_stage_set_title (CLUTTER_STAGE (stage), "Multi-layout");
390   g_signal_connect (stage, "destroy", G_CALLBACK (clutter_main_quit), NULL);
391   clutter_actor_show (stage);
392 
393   /* the layout manager for the main container */
394   manager = multi_layout_new ();
395   multi_layout_set_spacing ((MultiLayout *) manager, PADDING);
396 
397   margin.top = margin.bottom = margin.left = margin.right = PADDING;
398 
399   /* our main container, centered on the stage */
400   box = clutter_actor_new ();
401   clutter_actor_set_margin (box, &margin);
402   clutter_actor_set_layout_manager (box, manager);
403   clutter_actor_set_size (box, BOX_SIZE, BOX_SIZE);
404   clutter_actor_add_constraint (box, clutter_align_constraint_new (stage, CLUTTER_ALIGN_BOTH, 0.5));
405   clutter_actor_add_child (stage, box);
406 
407   for (i = 0; i < N_RECTS; i++)
408     {
409       ClutterActor *rect = clutter_actor_new ();
410       ClutterColor color;
411 
412       clutter_color_from_hls (&color,
413                               360.0 / N_RECTS * i,
414                               0.5,
415                               0.8);
416 
417       color.alpha = 128 + 128 / N_RECTS * i;
418 
419       /* elements on the layout */
420       clutter_actor_set_size (rect, RECT_SIZE, RECT_SIZE);
421       clutter_actor_set_pivot_point (rect, .5f, .5f);
422       clutter_actor_set_background_color (rect, &color);
423       clutter_actor_set_opacity (rect, 0);
424       clutter_actor_set_reactive (rect, TRUE);
425 
426       /* explicit transition that fades in the element; the delay on
427        * the transition staggers the fade depending on the index
428        */
429       transition = clutter_property_transition_new ("opacity");
430       clutter_timeline_set_duration (CLUTTER_TIMELINE (transition), 250);
431       clutter_timeline_set_delay (CLUTTER_TIMELINE (transition), i * 50);
432       clutter_transition_set_from (transition, G_TYPE_UINT, 0);
433       clutter_transition_set_to (transition, G_TYPE_UINT, 255);
434       clutter_actor_add_transition (rect, "fadeIn", transition);
435       g_object_unref (transition);
436 
437       /* we want all state transitions to be animated */
438       clutter_actor_set_easing_duration (rect, 250);
439       clutter_actor_set_easing_mode (rect, CLUTTER_EASE_OUT_CUBIC);
440 
441       clutter_actor_add_child (box, rect);
442 
443       /* simple hover effect */
444       g_signal_connect (rect, "enter-event", G_CALLBACK (on_enter), NULL);
445       g_signal_connect (rect, "leave-event", G_CALLBACK (on_leave), NULL);
446     }
447 
448   label = clutter_text_new ();
449   clutter_text_set_text (CLUTTER_TEXT (label),
450                          "Press t\t\342\236\236\tToggle layout\n"
451                          "Press q\t\342\236\236\tQuit");
452   clutter_actor_add_constraint (label, clutter_align_constraint_new (stage, CLUTTER_ALIGN_X_AXIS, 0.5));
453   clutter_actor_add_constraint (label, clutter_align_constraint_new (stage, CLUTTER_ALIGN_Y_AXIS, 0.95));
454   clutter_actor_add_child (stage, label);
455 
456   g_signal_connect (stage, "key-press-event", G_CALLBACK (on_key_press), box);
457 
458   clutter_main ();
459 
460   return EXIT_SUCCESS;
461 }
462