update to 1.10.4
[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="../../../../tests/interactive/test-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-color.h"
63 #include "clutter-content-private.h"
64 #include "clutter-marshal.h"
65 #include "clutter-paint-node.h"
66 #include "clutter-paint-nodes.h"
67 #include "clutter-private.h"
68
69 struct _ClutterCanvasPrivate
70 {
71   cairo_t *cr;
72
73   int width;
74   int height;
75
76   CoglBitmap *buffer;
77 };
78
79 enum
80 {
81   PROP_0,
82
83   PROP_WIDTH,
84   PROP_HEIGHT,
85
86   LAST_PROP
87 };
88
89 static GParamSpec *obj_props[LAST_PROP] = { NULL, };
90
91 enum
92 {
93   DRAW,
94
95   LAST_SIGNAL
96 };
97
98 static guint canvas_signals[LAST_SIGNAL] = { 0, };
99
100 static void clutter_content_iface_init (ClutterContentIface *iface);
101
102 G_DEFINE_TYPE_WITH_CODE (ClutterCanvas, clutter_canvas, G_TYPE_OBJECT,
103                          G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTENT,
104                                                 clutter_content_iface_init))
105
106 static void
107 clutter_cairo_context_draw_marshaller (GClosure     *closure,
108                                        GValue       *return_value,
109                                        guint         n_param_values,
110                                        const GValue *param_values,
111                                        gpointer      invocation_hint,
112                                        gpointer      marshal_data)
113 {
114   cairo_t *cr = g_value_get_boxed (&param_values[1]);
115
116   cairo_save (cr);
117
118   _clutter_marshal_BOOLEAN__BOXED_INT_INT (closure,
119                                            return_value,
120                                            n_param_values,
121                                            param_values,
122                                            invocation_hint,
123                                            marshal_data);
124
125   cairo_restore (cr);
126 }
127
128 static void
129 clutter_canvas_finalize (GObject *gobject)
130 {
131   ClutterCanvasPrivate *priv = CLUTTER_CANVAS (gobject)->priv;
132
133   if (priv->buffer != NULL)
134     {
135       cogl_object_unref (priv->buffer);
136       priv->buffer = NULL;
137     }
138
139   G_OBJECT_CLASS (clutter_canvas_parent_class)->finalize (gobject);
140 }
141
142 static void
143 clutter_canvas_set_property (GObject      *gobject,
144                              guint         prop_id,
145                              const GValue *value,
146                              GParamSpec   *pspec)
147 {
148   ClutterCanvasPrivate *priv = CLUTTER_CANVAS (gobject)->priv;
149
150   switch (prop_id)
151     {
152     case PROP_WIDTH:
153       if (priv->width != g_value_get_int (value))
154         {
155           priv->width = g_value_get_int (value);
156           clutter_content_invalidate (CLUTTER_CONTENT (gobject));
157         }
158       break;
159
160     case PROP_HEIGHT:
161       if (priv->height != g_value_get_int (value))
162         {
163           priv->height = g_value_get_int (value);
164           clutter_content_invalidate (CLUTTER_CONTENT (gobject));
165         }
166       break;
167
168     default:
169       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
170       break;
171     }
172 }
173
174 static void
175 clutter_canvas_get_property (GObject    *gobject,
176                              guint       prop_id,
177                              GValue     *value,
178                              GParamSpec *pspec)
179 {
180   ClutterCanvasPrivate *priv = CLUTTER_CANVAS (gobject)->priv;
181
182   switch (prop_id)
183     {
184     case PROP_WIDTH:
185       g_value_set_int (value, priv->width);
186       break;
187
188     case PROP_HEIGHT:
189       g_value_set_int (value, priv->height);
190       break;
191
192     default:
193       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
194       break;
195     }
196 }
197
198 static void
199 clutter_canvas_class_init (ClutterCanvasClass *klass)
200 {
201   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
202
203   g_type_class_add_private (klass, sizeof (ClutterCanvasPrivate));
204
205   /**
206    * ClutterCanvas:width:
207    *
208    * The width of the canvas.
209    *
210    * Since: 1.10
211    */
212   obj_props[PROP_WIDTH] =
213     g_param_spec_int ("width",
214                       P_("Width"),
215                       P_("The width of the canvas"),
216                       -1, G_MAXINT,
217                       -1,
218                       G_PARAM_READWRITE |
219                       G_PARAM_STATIC_STRINGS);
220
221   /**
222    * ClutterCanvas:height:
223    *
224    * The height of the canvas.
225    *
226    * Since: 1.10
227    */
228   obj_props[PROP_HEIGHT] =
229     g_param_spec_int ("height",
230                       P_("Height"),
231                       P_("The height of the canvas"),
232                       -1, G_MAXINT,
233                       -1,
234                       G_PARAM_READWRITE |
235                       G_PARAM_STATIC_STRINGS);
236
237   /**
238    * ClutterCanvas::draw:
239    * @canvas: the #ClutterCanvas that emitted the signal
240    * @cr: the Cairo context used to draw
241    * @width: the width of the @canvas
242    * @height: the height of the @canvas
243    *
244    * The #ClutterCanvas::draw signal is emitted each time a canvas is
245    * invalidated.
246    *
247    * It is safe to connect multiple handlers to this signal: each
248    * handler invocation will be automatically protected by cairo_save()
249    * and cairo_restore() pairs.
250    *
251    * Return value: %TRUE if the signal emission should stop, and
252    *   %FALSE otherwise
253    *
254    * Since: 1.10
255    */
256   canvas_signals[DRAW] =
257     g_signal_new (I_("draw"),
258                   G_TYPE_FROM_CLASS (klass),
259                   G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE,
260                   G_STRUCT_OFFSET (ClutterCanvasClass, draw),
261                   _clutter_boolean_handled_accumulator, NULL,
262                   clutter_cairo_context_draw_marshaller,
263                   G_TYPE_BOOLEAN, 3,
264                   CAIRO_GOBJECT_TYPE_CONTEXT,
265                   G_TYPE_INT,
266                   G_TYPE_INT);
267
268   gobject_class->set_property = clutter_canvas_set_property;
269   gobject_class->get_property = clutter_canvas_get_property;
270   gobject_class->finalize = clutter_canvas_finalize;
271
272   g_object_class_install_properties (gobject_class, LAST_PROP, obj_props);
273 }
274
275 static void
276 clutter_canvas_init (ClutterCanvas *self)
277 {
278   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, CLUTTER_TYPE_CANVAS,
279                                             ClutterCanvasPrivate);
280
281   self->priv->width = -1;
282   self->priv->height = -1;
283 }
284
285 static void
286 clutter_canvas_paint_content (ClutterContent   *content,
287                               ClutterActor     *actor,
288                               ClutterPaintNode *root)
289 {
290   ClutterCanvas *self = CLUTTER_CANVAS (content);
291   ClutterPaintNode *node;
292   CoglTexture *texture;
293   ClutterActorBox box;
294   ClutterColor color;
295   guint8 paint_opacity;
296   ClutterScalingFilter min_f, mag_f;
297
298   if (self->priv->buffer == NULL)
299     return;
300
301   texture = cogl_texture_new_from_bitmap (self->priv->buffer,
302                                           COGL_TEXTURE_NO_SLICING,
303                                           CLUTTER_CAIRO_FORMAT_ARGB32);
304   if (texture == NULL)
305     return;
306
307   clutter_actor_get_content_box (actor, &box);
308   paint_opacity = clutter_actor_get_paint_opacity (actor);
309   clutter_actor_get_content_scaling_filters (actor, &min_f, &mag_f);
310
311   color.red = paint_opacity;
312   color.green = paint_opacity;
313   color.blue = paint_opacity;
314   color.alpha = paint_opacity;
315
316   node = clutter_texture_node_new (texture, &color, min_f, mag_f);
317   cogl_object_unref (texture);
318
319   clutter_paint_node_set_name (node, "Canvas");
320   clutter_paint_node_add_rectangle (node, &box);
321   clutter_paint_node_add_child (root, node);
322   clutter_paint_node_unref (node);
323 }
324
325 static void
326 clutter_canvas_emit_draw (ClutterCanvas *self)
327 {
328   ClutterCanvasPrivate *priv = self->priv;
329   cairo_surface_t *surface;
330   gboolean mapped_buffer;
331   unsigned char *data;
332   CoglBuffer *buffer;
333   gboolean res;
334   cairo_t *cr;
335
336   g_assert (priv->width > 0 && priv->width > 0);
337
338   if (priv->buffer == NULL)
339     {
340       CoglContext *ctx;
341
342       ctx = clutter_backend_get_cogl_context (clutter_get_default_backend ());
343       priv->buffer = cogl_bitmap_new_with_size (ctx,
344                                                 priv->width,
345                                                 priv->height,
346                                                 CLUTTER_CAIRO_FORMAT_ARGB32);
347     }
348
349   buffer = COGL_BUFFER (cogl_bitmap_get_buffer (priv->buffer));
350   if (buffer == NULL)
351     return;
352
353   cogl_buffer_set_update_hint (buffer, COGL_BUFFER_UPDATE_HINT_DYNAMIC);
354
355   data = cogl_buffer_map (buffer,
356                           COGL_BUFFER_ACCESS_READ_WRITE,
357                           COGL_BUFFER_MAP_HINT_DISCARD);
358
359   if (data != NULL)
360     {
361       int bitmap_stride = cogl_bitmap_get_rowstride (priv->buffer);
362
363       surface = cairo_image_surface_create_for_data (data,
364                                                      CAIRO_FORMAT_ARGB32,
365                                                      priv->width,
366                                                      priv->height,
367                                                      bitmap_stride);
368       mapped_buffer = TRUE;
369     }
370   else
371     {
372       surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
373                                             priv->width,
374                                             priv->height);
375
376       mapped_buffer = FALSE;
377     }
378
379   self->priv->cr = cr = cairo_create (surface);
380
381   g_signal_emit (self, canvas_signals[DRAW], 0,
382                  cr, priv->width, priv->height,
383                  &res);
384
385   self->priv->cr = NULL;
386   cairo_destroy (cr);
387
388   if (mapped_buffer)
389     cogl_buffer_unmap (buffer);
390   else
391     {
392       int size = cairo_image_surface_get_stride (surface) * priv->height;
393       cogl_buffer_set_data (buffer,
394                             0,
395                             cairo_image_surface_get_data (surface),
396                             size);
397     }
398
399   cairo_surface_destroy (surface);
400 }
401
402 static void
403 clutter_canvas_invalidate (ClutterContent *content)
404 {
405   ClutterCanvas *self = CLUTTER_CANVAS (content);
406   ClutterCanvasPrivate *priv = self->priv;
407
408   if (priv->buffer != NULL)
409     {
410       cogl_object_unref (priv->buffer);
411       priv->buffer = NULL;
412     }
413
414   if (priv->width <= 0 || priv->height <= 0)
415     return;
416
417   clutter_canvas_emit_draw (self);
418 }
419
420 static gboolean
421 clutter_canvas_get_preferred_size (ClutterContent *content,
422                                    gfloat         *width,
423                                    gfloat         *height)
424 {
425   ClutterCanvasPrivate *priv = CLUTTER_CANVAS (content)->priv;
426
427   if (priv->width < 0 || priv->height < 0)
428     return FALSE;
429
430   if (width != NULL)
431     *width = priv->width;
432
433   if (height != NULL)
434     *height = priv->height;
435
436   return TRUE;
437 }
438
439 static void
440 clutter_content_iface_init (ClutterContentIface *iface)
441 {
442   iface->invalidate = clutter_canvas_invalidate;
443   iface->paint_content = clutter_canvas_paint_content;
444   iface->get_preferred_size = clutter_canvas_get_preferred_size;
445 }
446
447 /**
448  * clutter_canvas_new:
449  *
450  * Creates a new instance of #ClutterCanvas.
451  *
452  * You should call clutter_canvas_set_size() to set the size of the canvas.
453  *
454  * You should call clutter_content_invalidate() every time you wish to
455  * draw the contents of the canvas.
456  *
457  * Return value: (transfer full): The newly allocated instance of
458  *   #ClutterCanvas. Use g_object_unref() when done.
459  *
460  * Since: 1.10
461  */
462 ClutterContent *
463 clutter_canvas_new (void)
464 {
465   return g_object_new (CLUTTER_TYPE_CANVAS, NULL);
466 }
467
468 /**
469  * clutter_canvas_set_size:
470  * @canvas: a #ClutterCanvas
471  * @width: the width of the canvas, in pixels
472  * @height: the height of the canvas, in pixels
473  *
474  * Sets the size of the @canvas.
475  *
476  * This function will cause the @canvas to be invalidated.
477  *
478  * Since: 1.10
479  */
480 void
481 clutter_canvas_set_size (ClutterCanvas *canvas,
482                          int            width,
483                          int            height)
484 {
485   GObject *obj;
486   gboolean width_changed = FALSE, height_changed = FALSE;
487
488   g_return_if_fail (CLUTTER_IS_CANVAS (canvas));
489   g_return_if_fail (width >= -1 && height >= -1);
490
491   obj = G_OBJECT (canvas);
492
493   g_object_freeze_notify (obj);
494
495   if (canvas->priv->width != width)
496     {
497       canvas->priv->width = width;
498       width_changed = TRUE;
499
500       g_object_notify_by_pspec (obj, obj_props[PROP_WIDTH]);
501     }
502
503   if (canvas->priv->height != height)
504     {
505       canvas->priv->height = height;
506       height_changed = TRUE;
507
508       g_object_notify_by_pspec (obj, obj_props[PROP_HEIGHT]);
509     }
510
511   if (width_changed || height_changed)
512     clutter_content_invalidate (CLUTTER_CONTENT (canvas));
513
514   g_object_thaw_notify (obj);
515 }