Release Clutter 1.11.4 (snapshot)
[profile/ivi/clutter.git] / clutter / clutter-canvas.c
1 /*
2  * Clutter.
3  *
4  * An OpenGL based 'interactive canvas' library.
5  *
6  * Copyright (C) 2012  Intel Corporation.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
20  *
21  * Author:
22  *   Emmanuele Bassi <ebassi@linux.intel.com>
23  */
24
25 /**
26  * SECTION:clutter-canvas
27  * @Title: ClutterCanvas
28  * @Short_Description: Content for 2D painting
29  * @See_Also: #ClutterContent
30  *
31  * The #ClutterCanvas class is a #ClutterContent implementation that allows
32  * drawing using the Cairo API on a 2D surface.
33  *
34  * In order to draw on a #ClutterCanvas, you should connect a handler to the
35  * #ClutterCanvas::draw signal; the signal will receive a #cairo_t context
36  * that can be used to draw. #ClutterCanvas will emit the #ClutterCanvas::draw
37  * signal when invalidated using clutter_content_invalidate().
38  *
39  * <informalexample id="canvas-example">
40  *   <programlisting>
41  * <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" parse="text" href="../../../../examples/canvas.c">
42  *   <xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback>
43  * </xi:include>
44  *   </programlisting>
45  * </informalexample>
46  *
47  * #ClutterCanvas is available since Clutter 1.10.
48  */
49
50 #ifdef HAVE_CONFIG_H
51 #include "config.h"
52 #endif
53
54 #include <cogl/cogl.h>
55 #include <cairo-gobject.h>
56
57 #include "clutter-canvas.h"
58
59 #define CLUTTER_ENABLE_EXPERIMENTAL_API
60
61 #include "clutter-backend.h"
62 #include "clutter-cairo.h"
63 #include "clutter-color.h"
64 #include "clutter-content-private.h"
65 #include "clutter-marshal.h"
66 #include "clutter-paint-node.h"
67 #include "clutter-paint-nodes.h"
68 #include "clutter-private.h"
69
70 struct _ClutterCanvasPrivate
71 {
72   cairo_t *cr;
73
74   int width;
75   int height;
76
77   CoglBitmap *buffer;
78 };
79
80 enum
81 {
82   PROP_0,
83
84   PROP_WIDTH,
85   PROP_HEIGHT,
86
87   LAST_PROP
88 };
89
90 static GParamSpec *obj_props[LAST_PROP] = { NULL, };
91
92 enum
93 {
94   DRAW,
95
96   LAST_SIGNAL
97 };
98
99 static guint canvas_signals[LAST_SIGNAL] = { 0, };
100
101 static void clutter_content_iface_init (ClutterContentIface *iface);
102
103 G_DEFINE_TYPE_WITH_CODE (ClutterCanvas, clutter_canvas, G_TYPE_OBJECT,
104                          G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTENT,
105                                                 clutter_content_iface_init))
106
107 static void
108 clutter_cairo_context_draw_marshaller (GClosure     *closure,
109                                        GValue       *return_value,
110                                        guint         n_param_values,
111                                        const GValue *param_values,
112                                        gpointer      invocation_hint,
113                                        gpointer      marshal_data)
114 {
115   cairo_t *cr = g_value_get_boxed (&param_values[1]);
116
117   cairo_save (cr);
118
119   _clutter_marshal_BOOLEAN__BOXED_INT_INT (closure,
120                                            return_value,
121                                            n_param_values,
122                                            param_values,
123                                            invocation_hint,
124                                            marshal_data);
125
126   cairo_restore (cr);
127 }
128
129 static void
130 clutter_canvas_finalize (GObject *gobject)
131 {
132   ClutterCanvasPrivate *priv = CLUTTER_CANVAS (gobject)->priv;
133
134   if (priv->buffer != NULL)
135     {
136       cogl_object_unref (priv->buffer);
137       priv->buffer = NULL;
138     }
139
140   G_OBJECT_CLASS (clutter_canvas_parent_class)->finalize (gobject);
141 }
142
143 static void
144 clutter_canvas_set_property (GObject      *gobject,
145                              guint         prop_id,
146                              const GValue *value,
147                              GParamSpec   *pspec)
148 {
149   ClutterCanvasPrivate *priv = CLUTTER_CANVAS (gobject)->priv;
150
151   switch (prop_id)
152     {
153     case PROP_WIDTH:
154       if (priv->width != g_value_get_int (value))
155         {
156           priv->width = g_value_get_int (value);
157           clutter_content_invalidate (CLUTTER_CONTENT (gobject));
158         }
159       break;
160
161     case PROP_HEIGHT:
162       if (priv->height != g_value_get_int (value))
163         {
164           priv->height = g_value_get_int (value);
165           clutter_content_invalidate (CLUTTER_CONTENT (gobject));
166         }
167       break;
168
169     default:
170       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
171       break;
172     }
173 }
174
175 static void
176 clutter_canvas_get_property (GObject    *gobject,
177                              guint       prop_id,
178                              GValue     *value,
179                              GParamSpec *pspec)
180 {
181   ClutterCanvasPrivate *priv = CLUTTER_CANVAS (gobject)->priv;
182
183   switch (prop_id)
184     {
185     case PROP_WIDTH:
186       g_value_set_int (value, priv->width);
187       break;
188
189     case PROP_HEIGHT:
190       g_value_set_int (value, priv->height);
191       break;
192
193     default:
194       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
195       break;
196     }
197 }
198
199 static void
200 clutter_canvas_class_init (ClutterCanvasClass *klass)
201 {
202   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
203
204   g_type_class_add_private (klass, sizeof (ClutterCanvasPrivate));
205
206   /**
207    * ClutterCanvas:width:
208    *
209    * The width of the canvas.
210    *
211    * Since: 1.10
212    */
213   obj_props[PROP_WIDTH] =
214     g_param_spec_int ("width",
215                       P_("Width"),
216                       P_("The width of the canvas"),
217                       -1, G_MAXINT,
218                       -1,
219                       G_PARAM_READWRITE |
220                       G_PARAM_STATIC_STRINGS);
221
222   /**
223    * ClutterCanvas:height:
224    *
225    * The height of the canvas.
226    *
227    * Since: 1.10
228    */
229   obj_props[PROP_HEIGHT] =
230     g_param_spec_int ("height",
231                       P_("Height"),
232                       P_("The height of the canvas"),
233                       -1, G_MAXINT,
234                       -1,
235                       G_PARAM_READWRITE |
236                       G_PARAM_STATIC_STRINGS);
237
238   /**
239    * ClutterCanvas::draw:
240    * @canvas: the #ClutterCanvas that emitted the signal
241    * @cr: the Cairo context used to draw
242    * @width: the width of the @canvas
243    * @height: the height of the @canvas
244    *
245    * The #ClutterCanvas::draw signal is emitted each time a canvas is
246    * invalidated.
247    *
248    * It is safe to connect multiple handlers to this signal: each
249    * handler invocation will be automatically protected by cairo_save()
250    * and cairo_restore() pairs.
251    *
252    * Return value: %TRUE if the signal emission should stop, and
253    *   %FALSE otherwise
254    *
255    * Since: 1.10
256    */
257   canvas_signals[DRAW] =
258     g_signal_new (I_("draw"),
259                   G_TYPE_FROM_CLASS (klass),
260                   G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE,
261                   G_STRUCT_OFFSET (ClutterCanvasClass, draw),
262                   _clutter_boolean_handled_accumulator, NULL,
263                   clutter_cairo_context_draw_marshaller,
264                   G_TYPE_BOOLEAN, 3,
265                   CAIRO_GOBJECT_TYPE_CONTEXT,
266                   G_TYPE_INT,
267                   G_TYPE_INT);
268
269   gobject_class->set_property = clutter_canvas_set_property;
270   gobject_class->get_property = clutter_canvas_get_property;
271   gobject_class->finalize = clutter_canvas_finalize;
272
273   g_object_class_install_properties (gobject_class, LAST_PROP, obj_props);
274 }
275
276 static void
277 clutter_canvas_init (ClutterCanvas *self)
278 {
279   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, CLUTTER_TYPE_CANVAS,
280                                             ClutterCanvasPrivate);
281
282   self->priv->width = -1;
283   self->priv->height = -1;
284 }
285
286 static void
287 clutter_canvas_paint_content (ClutterContent   *content,
288                               ClutterActor     *actor,
289                               ClutterPaintNode *root)
290 {
291   ClutterCanvas *self = CLUTTER_CANVAS (content);
292   ClutterPaintNode *node;
293   CoglTexture *texture;
294   ClutterActorBox box;
295   ClutterColor color;
296   guint8 paint_opacity;
297   ClutterScalingFilter min_f, mag_f;
298
299   if (self->priv->buffer == NULL)
300     return;
301
302   texture = cogl_texture_new_from_bitmap (self->priv->buffer,
303                                           COGL_TEXTURE_NO_SLICING,
304                                           CLUTTER_CAIRO_FORMAT_ARGB32);
305   if (texture == NULL)
306     return;
307
308   clutter_actor_get_content_box (actor, &box);
309   paint_opacity = clutter_actor_get_paint_opacity (actor);
310   clutter_actor_get_content_scaling_filters (actor, &min_f, &mag_f);
311
312   color.red = paint_opacity;
313   color.green = paint_opacity;
314   color.blue = paint_opacity;
315   color.alpha = paint_opacity;
316
317   node = clutter_texture_node_new (texture, &color, min_f, mag_f);
318   cogl_object_unref (texture);
319
320   clutter_paint_node_set_name (node, "Canvas");
321   clutter_paint_node_add_rectangle (node, &box);
322   clutter_paint_node_add_child (root, node);
323   clutter_paint_node_unref (node);
324 }
325
326 static void
327 clutter_canvas_emit_draw (ClutterCanvas *self)
328 {
329   ClutterCanvasPrivate *priv = self->priv;
330   cairo_surface_t *surface;
331   gboolean mapped_buffer;
332   unsigned char *data;
333   CoglBuffer *buffer;
334   gboolean res;
335   cairo_t *cr;
336
337   g_assert (priv->width > 0 && priv->width > 0);
338
339   if (priv->buffer == NULL)
340     {
341       CoglContext *ctx;
342
343       ctx = clutter_backend_get_cogl_context (clutter_get_default_backend ());
344       priv->buffer = cogl_bitmap_new_with_size (ctx,
345                                                 priv->width,
346                                                 priv->height,
347                                                 CLUTTER_CAIRO_FORMAT_ARGB32);
348     }
349
350   buffer = COGL_BUFFER (cogl_bitmap_get_buffer (priv->buffer));
351   if (buffer == NULL)
352     return;
353
354   cogl_buffer_set_update_hint (buffer, COGL_BUFFER_UPDATE_HINT_DYNAMIC);
355
356   data = cogl_buffer_map (buffer,
357                           COGL_BUFFER_ACCESS_READ_WRITE,
358                           COGL_BUFFER_MAP_HINT_DISCARD);
359
360   if (data != NULL)
361     {
362       int bitmap_stride = cogl_bitmap_get_rowstride (priv->buffer);
363
364       surface = cairo_image_surface_create_for_data (data,
365                                                      CAIRO_FORMAT_ARGB32,
366                                                      priv->width,
367                                                      priv->height,
368                                                      bitmap_stride);
369       mapped_buffer = TRUE;
370     }
371   else
372     {
373       surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
374                                             priv->width,
375                                             priv->height);
376
377       mapped_buffer = FALSE;
378     }
379
380   self->priv->cr = cr = cairo_create (surface);
381
382   g_signal_emit (self, canvas_signals[DRAW], 0,
383                  cr, priv->width, priv->height,
384                  &res);
385
386   self->priv->cr = NULL;
387   cairo_destroy (cr);
388
389   if (mapped_buffer)
390     cogl_buffer_unmap (buffer);
391   else
392     {
393       int size = cairo_image_surface_get_stride (surface) * priv->height;
394       cogl_buffer_set_data (buffer,
395                             0,
396                             cairo_image_surface_get_data (surface),
397                             size);
398     }
399
400   cairo_surface_destroy (surface);
401 }
402
403 static void
404 clutter_canvas_invalidate (ClutterContent *content)
405 {
406   ClutterCanvas *self = CLUTTER_CANVAS (content);
407   ClutterCanvasPrivate *priv = self->priv;
408
409   if (priv->buffer != NULL)
410     {
411       cogl_object_unref (priv->buffer);
412       priv->buffer = NULL;
413     }
414
415   if (priv->width <= 0 || priv->height <= 0)
416     return;
417
418   clutter_canvas_emit_draw (self);
419 }
420
421 static gboolean
422 clutter_canvas_get_preferred_size (ClutterContent *content,
423                                    gfloat         *width,
424                                    gfloat         *height)
425 {
426   ClutterCanvasPrivate *priv = CLUTTER_CANVAS (content)->priv;
427
428   if (priv->width < 0 || priv->height < 0)
429     return FALSE;
430
431   if (width != NULL)
432     *width = priv->width;
433
434   if (height != NULL)
435     *height = priv->height;
436
437   return TRUE;
438 }
439
440 static void
441 clutter_content_iface_init (ClutterContentIface *iface)
442 {
443   iface->invalidate = clutter_canvas_invalidate;
444   iface->paint_content = clutter_canvas_paint_content;
445   iface->get_preferred_size = clutter_canvas_get_preferred_size;
446 }
447
448 /**
449  * clutter_canvas_new:
450  *
451  * Creates a new instance of #ClutterCanvas.
452  *
453  * You should call clutter_canvas_set_size() to set the size of the canvas.
454  *
455  * You should call clutter_content_invalidate() every time you wish to
456  * draw the contents of the canvas.
457  *
458  * Return value: (transfer full): The newly allocated instance of
459  *   #ClutterCanvas. Use g_object_unref() when done.
460  *
461  * Since: 1.10
462  */
463 ClutterContent *
464 clutter_canvas_new (void)
465 {
466   return g_object_new (CLUTTER_TYPE_CANVAS, NULL);
467 }
468
469 /**
470  * clutter_canvas_set_size:
471  * @canvas: a #ClutterCanvas
472  * @width: the width of the canvas, in pixels
473  * @height: the height of the canvas, in pixels
474  *
475  * Sets the size of the @canvas.
476  *
477  * This function will cause the @canvas to be invalidated.
478  *
479  * Since: 1.10
480  */
481 void
482 clutter_canvas_set_size (ClutterCanvas *canvas,
483                          int            width,
484                          int            height)
485 {
486   GObject *obj;
487   gboolean width_changed = FALSE, height_changed = FALSE;
488
489   g_return_if_fail (CLUTTER_IS_CANVAS (canvas));
490   g_return_if_fail (width >= -1 && height >= -1);
491
492   obj = G_OBJECT (canvas);
493
494   g_object_freeze_notify (obj);
495
496   if (canvas->priv->width != width)
497     {
498       canvas->priv->width = width;
499       width_changed = TRUE;
500
501       g_object_notify_by_pspec (obj, obj_props[PROP_WIDTH]);
502     }
503
504   if (canvas->priv->height != height)
505     {
506       canvas->priv->height = height;
507       height_changed = TRUE;
508
509       g_object_notify_by_pspec (obj, obj_props[PROP_HEIGHT]);
510     }
511
512   if (width_changed || height_changed)
513     clutter_content_invalidate (CLUTTER_CONTENT (canvas));
514
515   g_object_thaw_notify (obj);
516 }