Adhere to frames' natural sizes
[clutter-sprite:clutter-sprite.git] / foo-clutter-sprite.c
1 /*
2  * Copyright (c) 2010, Intel Corporation.
3  *
4  * This program is free software; you can redistribute it and/or modify it
5  * under the terms and conditions of the GNU General Public License,
6  * version 2, as published by the Free Software Foundation.
7  *
8  * This program is distributed in the hope it will be useful, but WITHOUT
9  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
11  * more details.
12  *
13  * You should have received a copy of the GNU General Public License along with
14  * this program; if not, write to the Free Software Foundation, Inc.,
15  * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
16  *
17  * Author: Rob Staudinger <robsta@linux.intel.com>
18  */
19
20 /*
21  * TODO
22  * Hook up to model's signals and fix up the frames when it changes.
23  */
24
25 #include <stdbool.h>
26 #include <glib.h>
27 #include "foo-clutter-sprite.h"
28
29 G_DEFINE_TYPE (FooClutterSprite, foo_clutter_sprite, CLUTTER_TYPE_ACTOR)
30
31 #define GET_PRIVATE(o) \
32   (G_TYPE_INSTANCE_GET_PRIVATE ((o), FOO_TYPE_CLUTTER_SPRITE, FooClutterSpritePrivate))
33
34 enum
35 {
36   PROP_0,
37   PROP_MODEL,
38   PROP_FRAME_INDEX,
39   PROP_FRAME_PROGRESS,
40   PROP_IMAGES,
41   PROP_N_FRAMES,
42 };
43
44 typedef struct
45 {
46   int                index;
47   ClutterModelIter  *iter;
48   CoglMaterial      *material;
49   unsigned           width;
50   unsigned           height;
51   unsigned           next_width;
52   unsigned           next_height;
53 } CachedFrame;
54
55 /* Model columns */
56 enum
57 {
58   COL_TEXTURE = 0,
59   COL_MATERIAL,
60   COL_WIDTH,
61   COL_HEIGHT,
62   N_COLUMNS
63 };
64
65 typedef struct
66 {
67   ClutterModel  *model;
68   float          frame_progress;
69   CachedFrame    cached_frame;
70 } FooClutterSpritePrivate;
71
72 /*
73  * CachedFrame
74  */
75
76 static void
77 cached_frame_update (CachedFrame      *frame,
78                      ClutterModel     *model,
79                      unsigned          index)
80 {
81   ClutterModelIter  *next;
82
83   /* Shortcuts */
84   if (index == frame->index)
85   {
86     /* Fading between the frames. */
87     return;
88
89   } else if (frame->iter &&
90              index == frame->index + 1) {
91
92     /* Advancing the frame. */
93     frame->iter = clutter_model_iter_next (frame->iter);
94
95   } else {
96
97     if (frame->iter)
98     {
99       g_object_unref (frame->iter);
100       frame->iter = NULL;
101     }
102
103     frame->iter = clutter_model_get_iter_at_row (model, index);
104   }
105
106   if (frame->material)
107   {
108     cogl_object_unref (frame->material);
109     frame->material = NULL;
110   }
111
112   next = clutter_model_iter_copy (frame->iter);
113   next = clutter_model_iter_next (next);
114   if (clutter_model_iter_is_last (next))
115   {
116     g_object_unref (next);
117     next = clutter_model_get_first_iter (model);
118   }
119
120   frame->index = index;
121
122   clutter_model_iter_get (frame->iter,
123                           COL_MATERIAL, &frame->material,
124                           COL_WIDTH, &frame->width,
125                           COL_HEIGHT, &frame->height,
126                           -1);
127
128   cogl_object_ref (frame->material);
129
130   clutter_model_iter_get (next,
131                           COL_WIDTH, &frame->next_width,
132                           COL_HEIGHT, &frame->next_height,
133                           -1);
134
135   g_object_unref (next);
136 }
137
138 static void
139 cached_frame_invalidate (CachedFrame *frame)
140 {
141   frame->index = -1;
142
143   if (frame->iter)
144   {
145     g_object_unref (frame->iter);
146     frame->iter = NULL;
147   }
148
149   if (frame->material)
150   {
151     cogl_object_unref (frame->material);
152     frame->material = NULL;
153   }
154 }
155
156 static unsigned
157 cached_frame_get_width (CachedFrame *frame,
158                         float        progress)
159 {
160   unsigned index;
161   float    crossfade;
162
163   index = progress;
164   crossfade = progress - index;
165
166   return frame->width + ((int) frame->next_width - (int) frame->width) * crossfade;
167 }
168
169 static unsigned
170 cached_frame_get_height (CachedFrame  *frame,
171                          float         progress)
172 {
173   unsigned index;
174   float    crossfade;
175
176   index = progress;
177   crossfade = progress - index;
178
179   return frame->height + ((int) frame->next_height - (int) frame->height) * crossfade;
180 }
181
182 static void
183 cached_frame_paint (CachedFrame     *frame,
184                     ClutterActorBox *box,
185                     float            progress)
186 {
187   CoglMaterial  *material;
188   CoglColor      combine;
189   unsigned       index;
190   float          crossfade;
191
192   index = progress;
193   crossfade = progress - index;
194
195   cogl_color_set_from_4f (&combine, 0.0, 0.0, 0.0, 1 - crossfade);
196   material = cogl_material_copy (frame->material);
197   cogl_material_set_layer_combine_constant (material, 1, &combine);
198
199   cogl_set_source (material);
200   cogl_rectangle (box->x1, box->y1, box->x2, box->y2);
201
202   cogl_object_unref (material);
203 }
204
205 /*
206  * FooClutterSprite
207  */
208
209 static void
210 foo_clutter_sprite_set_images (FooClutterSprite  *self,
211                                char const       **images);
212
213 bool
214 _free_model_row (ClutterModel     *model,
215                  ClutterModelIter *iter,
216                  void             *data)
217 {
218   CoglHandle   texture = NULL;
219   CoglObject  *material = NULL;
220
221   clutter_model_iter_get (iter,
222                           COL_TEXTURE, &texture,
223                           COL_MATERIAL, &material,
224                           -1);
225
226   if (texture)
227     cogl_handle_unref (texture);
228
229   if (material)
230     cogl_object_unref (material);
231
232   return true;
233 }
234
235 static void
236 _model_filter_changed (ClutterModel     *model,
237                        FooClutterSprite *self)
238 {
239   g_warning ("Sorting is not implemented yet");
240 }
241
242 static void
243 _model_row_added (ClutterModel      *model,
244                   ClutterModelIter  *iter,
245                   FooClutterSprite  *self)
246 {
247   g_warning ("Changing the model is not implemented yet");
248 }
249
250 static void
251 _model_row_changed (ClutterModel      *model,
252                     ClutterModelIter  *iter,
253                     FooClutterSprite  *self)
254 {
255   g_warning ("Changing the model is not implemented yet");
256 }
257
258 static void
259 _model_row_removed (ClutterModel      *model,
260                     ClutterModelIter  *iter,
261                     FooClutterSprite  *self)
262 {
263   g_warning ("Changing the model is not implemented yet");
264 }
265
266 static void
267 _model_sort_changed (ClutterModel     *model,
268                      FooClutterSprite *self)
269 {
270   g_warning ("Sorting is not implemented yet");
271 }
272
273 static void
274 reset (FooClutterSprite *self)
275 {
276   FooClutterSpritePrivate *priv = GET_PRIVATE (self);
277
278   if (priv->model)
279   {
280     g_signal_handlers_disconnect_by_func (priv->model,
281                                           G_CALLBACK (_model_filter_changed),
282                                           self);
283     g_signal_handlers_disconnect_by_func (priv->model,
284                                           G_CALLBACK (_model_row_added),
285                                           self);
286     g_signal_handlers_disconnect_by_func (priv->model,
287                                           G_CALLBACK (_model_row_changed),
288                                           self);
289     g_signal_handlers_disconnect_by_func (priv->model,
290                                           G_CALLBACK (_model_row_removed),
291                                           self);
292     g_signal_handlers_disconnect_by_func (priv->model,
293                                           G_CALLBACK (_model_sort_changed),
294                                           self);
295
296     clutter_model_foreach (priv->model,
297                            (ClutterModelForeachFunc) _free_model_row,
298                            NULL);
299     g_object_unref (priv->model);
300     priv->model = NULL;
301   }
302
303   cached_frame_invalidate (&priv->cached_frame);
304 }
305
306 static void
307 _get_property (GObject    *object,
308                unsigned    property_id,
309                GValue     *value,
310                GParamSpec *pspec)
311 {
312   switch (property_id)
313   {
314   case PROP_FRAME_INDEX:
315     g_value_set_uint (value,
316                       foo_clutter_sprite_get_frame_index (
317                         FOO_CLUTTER_SPRITE (object)));
318     break;
319   case PROP_FRAME_PROGRESS:
320     g_value_set_float (value,
321                        foo_clutter_sprite_get_frame_progress (
322                         FOO_CLUTTER_SPRITE (object)));
323     break;
324   case PROP_N_FRAMES:
325     g_value_set_uint (value,
326                       foo_clutter_sprite_get_n_frames (
327                         FOO_CLUTTER_SPRITE (object)));
328     break;
329   case PROP_MODEL:
330     g_value_set_pointer (value,
331                         foo_clutter_sprite_get_model (
332                           FOO_CLUTTER_SPRITE (object)));
333     break;
334   case PROP_IMAGES:
335     /* Contruct-only, mentioned here to satisfy the compiler. */
336   default:
337     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
338   }
339 }
340
341 static void
342 _set_property (GObject      *object,
343                unsigned      property_id,
344                const GValue *value,
345                GParamSpec   *pspec)
346 {
347   switch (property_id)
348   {
349   case PROP_FRAME_INDEX:
350     foo_clutter_sprite_set_frame_index (FOO_CLUTTER_SPRITE (object),
351                                         g_value_get_uint (value));
352     break;
353   case PROP_FRAME_PROGRESS:
354     foo_clutter_sprite_set_frame_progress (FOO_CLUTTER_SPRITE (object),
355                                            g_value_get_float (value));
356     break;
357   case PROP_IMAGES:
358     foo_clutter_sprite_set_images (FOO_CLUTTER_SPRITE (object),
359                                    (char const **) g_value_get_boxed (value));
360     break;
361   case PROP_MODEL:
362     foo_clutter_sprite_set_model (FOO_CLUTTER_SPRITE (object),
363                                   g_value_get_object (value));
364     break;
365   case PROP_N_FRAMES:
366     /* Read-only, mentioned here to satisfy the compiler. */
367   default:
368     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
369   }
370 }
371
372 static void
373 _get_preferred_width (ClutterActor  *actor,
374                       float          for_height,
375                       float         *min_width_p,
376                       float         *natural_width_p)
377 {
378   FooClutterSpritePrivate *priv = GET_PRIVATE (actor);
379   float width;
380
381   cached_frame_update (&priv->cached_frame, priv->model, priv->frame_progress);
382   width = cached_frame_get_width (&priv->cached_frame, priv->frame_progress);
383
384   if (min_width_p)
385     *min_width_p = width;
386
387   if (natural_width_p)
388     *natural_width_p = width;
389 }
390
391 static void
392 _get_preferred_height (ClutterActor *actor,
393                        float         for_width,
394                        float        *min_height_p,
395                        float        *natural_height_p)
396 {
397   FooClutterSpritePrivate *priv = GET_PRIVATE (actor);
398   float height;
399
400   cached_frame_update (&priv->cached_frame, priv->model, priv->frame_progress);
401   height = cached_frame_get_height (&priv->cached_frame, priv->frame_progress);
402
403   if (min_height_p)
404     *min_height_p = height;
405
406   if (natural_height_p)
407     *natural_height_p = height;
408 }
409
410 static void
411 _paint (ClutterActor *actor)
412 {
413   FooClutterSpritePrivate *priv = GET_PRIVATE (actor);
414
415   if (priv->model)
416   {
417     ClutterActorBox box;
418
419     g_return_if_fail (priv->frame_progress < clutter_model_get_n_rows (priv->model));
420
421     clutter_actor_get_allocation_box (actor, &box);
422     cached_frame_update (&priv->cached_frame, priv->model, priv->frame_progress);
423     cached_frame_paint (&priv->cached_frame, &box, priv->frame_progress);
424   }
425 }
426
427 static void
428 _finalize (GObject *object)
429 {
430   reset (FOO_CLUTTER_SPRITE (object));
431
432   G_OBJECT_CLASS (foo_clutter_sprite_parent_class)->finalize (object);
433 }
434
435 static void
436 foo_clutter_sprite_class_init (FooClutterSpriteClass *klass)
437 {
438   GObjectClass *object_class = G_OBJECT_CLASS (klass);
439   ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
440
441   g_type_class_add_private (klass, sizeof (FooClutterSpritePrivate));
442
443   object_class->get_property = _get_property;
444   object_class->set_property = _set_property;
445   object_class->finalize = _finalize;
446
447   actor_class->get_preferred_width = _get_preferred_width;
448   actor_class->get_preferred_height = _get_preferred_height;
449   actor_class->paint = _paint;
450
451   /* Convenience wrapper property for discrete animation,
452    * does not notify when changed. Hook up to "frame-progress" for that. */
453   g_object_class_install_property (object_class,
454                                    PROP_FRAME_INDEX,
455                                    g_param_spec_uint ("frame-index",
456                                                       "Frame index",
457                                                       "Current frame index",
458                                                       0, G_MAXUINT, 0,
459                                                       G_PARAM_READWRITE));
460
461   g_object_class_install_property (object_class,
462                                    PROP_FRAME_PROGRESS,
463                                    g_param_spec_float ("frame-progress",
464                                                        "Frame progress",
465                                                        "Continuous frame index for cross-fading",
466                                                        0, G_MAXFLOAT, 0,
467                                                        G_PARAM_CONSTRUCT |
468                                                        G_PARAM_READWRITE));
469
470   g_object_class_install_property (object_class,
471                                    PROP_IMAGES,
472                                    g_param_spec_boxed ("images",
473                                                        "Source images",
474                                                        "Images to create the sprite from",
475                                                        G_TYPE_STRV,
476                                                        G_PARAM_CONSTRUCT_ONLY |
477                                                        G_PARAM_WRITABLE));
478
479   g_object_class_install_property (object_class,
480                                    PROP_N_FRAMES,
481                                    g_param_spec_uint ("n-frames",
482                                                       "Number of frames",
483                                                       "Total number of frames",
484                                                       0, G_MAXUINT, 0,
485                                                       G_PARAM_READABLE));
486
487   g_object_class_install_property (object_class,
488                                    PROP_MODEL,
489                                    g_param_spec_object ("model",
490                                                         "Textures model",
491                                                         "Model that holds the source textures",
492                                                         CLUTTER_TYPE_MODEL,
493                                                         G_PARAM_READWRITE));
494 }
495
496 static void
497 foo_clutter_sprite_init (FooClutterSprite *self)
498 {
499   reset (self);
500 }
501
502 ClutterActor *
503 foo_clutter_sprite_new (void)
504 {
505   return g_object_new (FOO_TYPE_CLUTTER_SPRITE, NULL);
506 }
507
508 ClutterActor *
509 foo_clutter_sprite_new_from_images (char const **images)
510 {
511   return g_object_new (FOO_TYPE_CLUTTER_SPRITE,
512                        "images", images,
513                        NULL);
514 }
515
516 static void
517 foo_clutter_sprite_set_images (FooClutterSprite  *self,
518                                char const       **images)
519 {
520   ClutterModel  *model;
521   unsigned       n_images;
522   unsigned       i;
523
524   reset (self);
525
526   for (n_images = 0; images && images[n_images]; n_images++)
527     ;
528
529   if (n_images == 0)
530   {
531     return;
532   }
533
534   model = foo_clutter_sprite_create_model ();
535
536   for (i = 0; i < n_images; i++)
537   {
538     GError *error = NULL;
539     CoglHandle texture = cogl_texture_new_from_file (images[i],
540                                                      COGL_TEXTURE_NONE,
541                                                      COGL_PIXEL_FORMAT_ANY,
542                                                      &error);
543     if (error)
544     {
545       g_critical ("%s : %s", G_STRLOC, error->message);
546       g_clear_error (&error);
547
548     } else {
549
550       clutter_model_append (model, 0, texture, -1);
551     }
552   }
553
554   foo_clutter_sprite_set_model (self, model);
555   g_object_unref (model);
556 }
557
558 unsigned
559 foo_clutter_sprite_get_frame_index (FooClutterSprite  *self)
560 {
561   return foo_clutter_sprite_get_frame_progress (self);
562 }
563
564 void
565 foo_clutter_sprite_set_frame_index (FooClutterSprite  *self,
566                                     unsigned           frame_index)
567 {
568   foo_clutter_sprite_set_frame_progress (self, frame_index);
569 }
570
571 float
572 foo_clutter_sprite_get_frame_progress (FooClutterSprite  *self)
573 {
574   FooClutterSpritePrivate *priv = GET_PRIVATE (self);
575
576   g_return_val_if_fail (FOO_IS_CLUTTER_SPRITE (self), 0);
577
578   return priv->frame_progress;
579 }
580
581 void
582 foo_clutter_sprite_set_frame_progress (FooClutterSprite *self,
583                                        float             frame_progress)
584 {
585   FooClutterSpritePrivate *priv = GET_PRIVATE (self);
586
587   g_return_if_fail (FOO_IS_CLUTTER_SPRITE (self));
588
589   if (priv->model == NULL)
590   {
591     g_critical ("%s : Can not set frame-progress without frames", G_STRLOC);
592     return;
593   }
594
595   if (frame_progress >= clutter_model_get_n_rows (priv->model))
596   {
597     g_critical ("%s : frame-progress (%f) >= n-frames (%i)",
598                 G_STRLOC, frame_progress,
599                 clutter_model_get_n_rows (priv->model));
600     return;
601   }
602
603   if (frame_progress != priv->frame_progress)
604   {
605     priv->frame_progress = frame_progress;
606     g_object_notify (G_OBJECT (self), "frame-progress");
607     clutter_actor_queue_relayout (CLUTTER_ACTOR (self));
608   }
609 }
610
611 unsigned
612 foo_clutter_sprite_get_n_frames (FooClutterSprite *self)
613 {
614   FooClutterSpritePrivate *priv = GET_PRIVATE (self);
615
616   g_return_val_if_fail (FOO_IS_CLUTTER_SPRITE (self), 0);
617
618   if (priv->model)
619   {
620     return clutter_model_get_n_rows (priv->model);
621   }
622
623   return 0;
624 }
625
626 ClutterModel *
627 foo_clutter_sprite_get_model (FooClutterSprite *self)
628 {
629   FooClutterSpritePrivate *priv = GET_PRIVATE (self);
630
631   g_return_val_if_fail (FOO_IS_CLUTTER_SPRITE (self), 0);
632
633   return priv->model;
634 }
635
636 void
637 foo_clutter_sprite_set_model (FooClutterSprite  *self,
638                               ClutterModel      *model)
639 {
640   FooClutterSpritePrivate *priv = GET_PRIVATE (self);
641
642   g_return_if_fail (FOO_IS_CLUTTER_SPRITE (self));
643
644   if (model != priv->model)
645   {
646     reset (self);
647
648     if (model)
649     {
650       /* Create materials for cross-fading. */
651       CoglMaterial *first_material = NULL;
652       ClutterModelIter *iter;
653
654       for (iter = clutter_model_get_first_iter (model);
655            !clutter_model_iter_is_last (iter);
656            iter = clutter_model_iter_next (iter))
657       {
658         CoglMaterial      *material;
659         CoglHandle         texture = NULL;
660         ClutterModelIter  *next;
661         CoglHandle         next_texture = NULL;
662
663         clutter_model_iter_get (iter, 0, &texture, -1);
664
665         if (first_material == NULL)
666         {
667           GError *error = NULL;
668           material = cogl_material_new ();
669           cogl_material_set_layer_combine (material, 1,
670                                            "RGBA = INTERPOLATE (PREVIOUS,"
671                                                                "TEXTURE,"
672                                                                "CONSTANT[A])",
673                                            &error);
674           if (error)
675           {
676             g_critical ("%s : %s", G_STRLOC, error->message);
677             g_clear_error (&error);
678           }
679
680           first_material = material;
681
682         } else {
683
684           material = cogl_material_copy (first_material);
685         }
686
687         cogl_material_set_layer (material, 0, texture);
688         clutter_model_iter_set (iter,
689                                 COL_MATERIAL, material,
690                                 COL_WIDTH, cogl_texture_get_width (texture),
691                                 COL_HEIGHT, cogl_texture_get_height (texture),
692                                 -1);
693
694         /* Layer 1 references the next frame, so we can blend to it */
695         next = clutter_model_iter_copy (iter);
696         next = clutter_model_iter_next (next);
697         if (clutter_model_iter_is_last (next))
698         {
699           g_object_unref (next);
700           next = clutter_model_get_first_iter (model);
701         }
702
703         clutter_model_iter_get (next, 0, &next_texture, -1);
704         cogl_material_set_layer (material, 1, next_texture);
705         g_object_unref (next);
706       }
707       g_object_unref (iter);
708
709       priv->model = g_object_ref (model);
710       g_signal_connect (priv->model, "filter-changed",
711                         G_CALLBACK (_model_filter_changed), self);
712       g_signal_connect (priv->model, "row-added",
713                         G_CALLBACK (_model_row_added), self);
714       g_signal_connect (priv->model, "row-changed",
715                         G_CALLBACK (_model_row_changed), self);
716       g_signal_connect (priv->model, "row-removed",
717                         G_CALLBACK (_model_row_removed), self);
718       g_signal_connect (priv->model, "sort-changed",
719                         G_CALLBACK (_model_sort_changed), self);
720       g_object_notify (G_OBJECT (self), "model");
721     }
722
723     foo_clutter_sprite_set_frame_progress (self, 0);
724   }
725 }
726
727 ClutterModel *
728 foo_clutter_sprite_create_model (void)
729 {
730   ClutterModel *model;
731
732   model = clutter_list_model_new (N_COLUMNS,
733                                   G_TYPE_POINTER, "texture",
734                                   G_TYPE_POINTER, "material",
735                                   G_TYPE_UINT,    "width",
736                                   G_TYPE_UINT,    "height");
737
738   return model;
739 }