diff --git a/clutter/Makefile.am b/clutter/Makefile.am
index 9df651b85..7f65b97ee 100644
--- a/clutter/Makefile.am
+++ b/clutter/Makefile.am
@@ -65,6 +65,7 @@ source_h = \
$(srcdir)/clutter-box-layout.h \
$(srcdir)/clutter-brightness-contrast-effect.h \
$(srcdir)/clutter-cairo-texture.h \
+ $(srcdir)/clutter-canvas.h \
$(srcdir)/clutter-child-meta.h \
$(srcdir)/clutter-click-action.h \
$(srcdir)/clutter-cogl-compat.h \
@@ -145,6 +146,7 @@ source_c = \
$(srcdir)/clutter-box-layout.c \
$(srcdir)/clutter-brightness-contrast-effect.c \
$(srcdir)/clutter-cairo-texture.c \
+ $(srcdir)/clutter-canvas.c \
$(srcdir)/clutter-child-meta.c \
$(srcdir)/clutter-click-action.c \
$(srcdir)/clutter-clone.c \
diff --git a/clutter/clutter-cairo-texture.h b/clutter/clutter-cairo-texture.h
index c26b74177..78f341b7e 100644
--- a/clutter/clutter-cairo-texture.h
+++ b/clutter/clutter-cairo-texture.h
@@ -43,26 +43,6 @@ G_BEGIN_DECLS
#define CLUTTER_IS_CAIRO_TEXTURE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CLUTTER_TYPE_CAIRO_TEXTURE))
#define CLUTTER_CAIRO_TEXTURE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CLUTTER_TYPE_CAIRO_TEXTURE, ClutterCairoTextureClass))
-/**
- * CLUTTER_CAIRO_FORMAT_ARGB32:
- *
- * The #CoglPixelFormat to be used when uploading image data from
- * and to a Cairo image surface using %CAIRO_FORMAT_ARGB32 and
- * %CAIRO_FORMAT_RGB24 as #cairo_format_t.
- *
- * Since: 1.8
- */
-
-/* Cairo stores the data in native byte order as ARGB but Cogl's pixel
- * formats specify the actual byte order. Therefore we need to use a
- * different format depending on the architecture
- */
-#if G_BYTE_ORDER == G_LITTLE_ENDIAN
-#define CLUTTER_CAIRO_FORMAT_ARGB32 (COGL_PIXEL_FORMAT_BGRA_8888_PRE)
-#else
-#define CLUTTER_CAIRO_FORMAT_ARGB32 (COGL_PIXEL_FORMAT_ARGB_8888_PRE)
-#endif
-
typedef struct _ClutterCairoTexture ClutterCairoTexture;
typedef struct _ClutterCairoTextureClass ClutterCairoTextureClass;
typedef struct _ClutterCairoTexturePrivate ClutterCairoTexturePrivate;
diff --git a/clutter/clutter-canvas.c b/clutter/clutter-canvas.c
new file mode 100644
index 000000000..64c135d4b
--- /dev/null
+++ b/clutter/clutter-canvas.c
@@ -0,0 +1,507 @@
+/*
+ * Clutter.
+ *
+ * An OpenGL based 'interactive canvas' library.
+ *
+ * Copyright (C) 2012 Intel Corporation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * Author:
+ * Emmanuele Bassi
+ */
+
+/**
+ * SECTION:clutter-canvas
+ * @Title: ClutterCanvas
+ * @Short_Description: Content for 2D painting
+ * @See_Also: #ClutterContent
+ *
+ * The #ClutterCanvas class is a #ClutterContent implementation that allows
+ * drawing using the Cairo API on a 2D surface.
+ *
+ * In order to draw on a #ClutterCanvas, you should connect a handler to the
+ * #ClutterCanvas::draw signal; the signal will receive a #cairo_t context
+ * that can be used to draw. #ClutterCanvas will emit the #ClutterCanvas::draw
+ * signal when invalidated using clutter_content_invalidate().
+ *
+ *
+ *
+ *
+ * FIXME: MISSING XINCLUDE CONTENT
+ *
+ *
+ *
+ *
+ * #ClutterCanvas is available since Clutter 1.10.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include
+#include
+
+#include "clutter-canvas.h"
+
+#define CLUTTER_ENABLE_EXPERIMENTAL_API
+
+#include "clutter-backend.h"
+#include "clutter-color.h"
+#include "clutter-content-private.h"
+#include "clutter-marshal.h"
+#include "clutter-paint-node.h"
+#include "clutter-paint-nodes.h"
+#include "clutter-private.h"
+
+struct _ClutterCanvasPrivate
+{
+ cairo_t *cr;
+
+ int width;
+ int height;
+
+ CoglBitmap *buffer;
+};
+
+enum
+{
+ PROP_0,
+
+ PROP_WIDTH,
+ PROP_HEIGHT,
+
+ LAST_PROP
+};
+
+static GParamSpec *obj_props[LAST_PROP] = { NULL, };
+
+enum
+{
+ DRAW,
+
+ LAST_SIGNAL
+};
+
+static guint canvas_signals[LAST_SIGNAL] = { 0, };
+
+static void clutter_content_iface_init (ClutterContentIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (ClutterCanvas, clutter_canvas, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTENT,
+ clutter_content_iface_init))
+
+static void
+clutter_cairo_context_draw_marshaller (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data)
+{
+ cairo_t *cr = g_value_get_boxed (¶m_values[1]);
+
+ cairo_save (cr);
+
+ _clutter_marshal_BOOLEAN__BOXED (closure,
+ return_value,
+ n_param_values,
+ param_values,
+ invocation_hint,
+ marshal_data);
+
+ cairo_restore (cr);
+}
+
+static void
+clutter_canvas_finalize (GObject *gobject)
+{
+ ClutterCanvasPrivate *priv = CLUTTER_CANVAS (gobject)->priv;
+
+ if (priv->buffer != NULL)
+ {
+ cogl_object_unref (priv->buffer);
+ priv->buffer = NULL;
+ }
+
+ G_OBJECT_CLASS (clutter_canvas_parent_class)->finalize (gobject);
+}
+
+static void
+clutter_canvas_set_property (GObject *gobject,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ ClutterCanvasPrivate *priv = CLUTTER_CANVAS (gobject)->priv;
+
+ switch (prop_id)
+ {
+ case PROP_WIDTH:
+ if (priv->width != g_value_get_int (value))
+ {
+ priv->width = g_value_get_int (value);
+ clutter_content_invalidate (CLUTTER_CONTENT (gobject));
+ }
+ break;
+
+ case PROP_HEIGHT:
+ if (priv->height != g_value_get_int (value))
+ {
+ priv->height = g_value_get_int (value);
+ clutter_content_invalidate (CLUTTER_CONTENT (gobject));
+ }
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+clutter_canvas_get_property (GObject *gobject,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ClutterCanvasPrivate *priv = CLUTTER_CANVAS (gobject)->priv;
+
+ switch (prop_id)
+ {
+ case PROP_WIDTH:
+ g_value_set_int (value, priv->width);
+ break;
+
+ case PROP_HEIGHT:
+ g_value_set_int (value, priv->height);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+clutter_canvas_class_init (ClutterCanvasClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (ClutterCanvasPrivate));
+
+ /**
+ * ClutterCanvas:width:
+ *
+ * The width of the canvas.
+ *
+ * Since: 1.10
+ */
+ obj_props[PROP_WIDTH] =
+ g_param_spec_int ("width",
+ P_("Width"),
+ P_("The width of the canvas"),
+ -1, G_MAXINT,
+ -1,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
+
+ /**
+ * ClutterCanvas:height:
+ *
+ * The height of the canvas.
+ *
+ * Since: 1.10
+ */
+ obj_props[PROP_HEIGHT] =
+ g_param_spec_int ("height",
+ P_("Height"),
+ P_("The height of the canvas"),
+ -1, G_MAXINT,
+ -1,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
+
+ /**
+ * ClutterCanvas::draw:
+ * @canvas: the #ClutterCanvas that emitted the signal
+ * @cr: the Cairo context used to draw
+ *
+ * The #ClutterCanvas::draw signal is emitted each time a canvas is
+ * invalidated.
+ *
+ * It is safe to connect multiple handlers to this signal: each
+ * handler invocation will be automatically protected by cairo_save()
+ * and cairo_restore() pairs.
+ *
+ * Return value: %TRUE if the signal emission should stop, and
+ * %FALSE otherwise
+ *
+ * Since: 1.10
+ */
+ canvas_signals[DRAW] =
+ g_signal_new (I_("draw"),
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE,
+ G_STRUCT_OFFSET (ClutterCanvasClass, draw),
+ _clutter_boolean_handled_accumulator, NULL,
+ clutter_cairo_context_draw_marshaller,
+ G_TYPE_BOOLEAN, 1,
+ CAIRO_GOBJECT_TYPE_CONTEXT);
+
+ gobject_class->set_property = clutter_canvas_set_property;
+ gobject_class->get_property = clutter_canvas_get_property;
+ gobject_class->finalize = clutter_canvas_finalize;
+
+ g_object_class_install_properties (gobject_class, LAST_PROP, obj_props);
+}
+
+static void
+clutter_canvas_init (ClutterCanvas *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, CLUTTER_TYPE_CANVAS,
+ ClutterCanvasPrivate);
+
+ self->priv->width = -1;
+ self->priv->height = -1;
+}
+
+static void
+clutter_canvas_paint_content (ClutterContent *content,
+ ClutterActor *actor,
+ ClutterPaintNode *root)
+{
+ ClutterCanvas *self = CLUTTER_CANVAS (content);
+ ClutterPaintNode *node;
+ CoglTexture *texture;
+ ClutterActorBox box;
+ ClutterColor color;
+ guint8 paint_opacity;
+
+ if (self->priv->buffer == NULL)
+ return;
+
+ texture = cogl_texture_new_from_bitmap (self->priv->buffer,
+ COGL_TEXTURE_NO_SLICING,
+ CLUTTER_CAIRO_FORMAT_ARGB32);
+ if (texture == NULL)
+ return;
+
+ clutter_actor_get_content_box (actor, &box);
+ paint_opacity = clutter_actor_get_paint_opacity (actor);
+
+ color.red = paint_opacity;
+ color.green = paint_opacity;
+ color.blue = paint_opacity;
+ color.alpha = paint_opacity;
+
+ node = clutter_texture_node_new (texture, &color);
+ cogl_object_unref (texture);
+
+ clutter_paint_node_set_name (node, "Canvas");
+ clutter_paint_node_add_rectangle (node, &box);
+ clutter_paint_node_add_child (root, node);
+ clutter_paint_node_unref (node);
+}
+
+static void
+clutter_canvas_emit_draw (ClutterCanvas *self)
+{
+ ClutterCanvasPrivate *priv = self->priv;
+ cairo_surface_t *surface;
+ gboolean mapped_buffer;
+ unsigned char *data;
+ CoglBuffer *buffer;
+ gboolean res;
+ cairo_t *cr;
+
+ g_assert (priv->width >= 0 && priv->width >= 0);
+
+ if (priv->buffer == NULL)
+ {
+ CoglContext *ctx;
+
+ ctx = clutter_backend_get_cogl_context (clutter_get_default_backend ());
+ priv->buffer = cogl_bitmap_new_with_size (ctx,
+ priv->width,
+ priv->height,
+ CLUTTER_CAIRO_FORMAT_ARGB32);
+ }
+
+ buffer = COGL_BUFFER (cogl_bitmap_get_buffer (priv->buffer));
+ if (buffer == NULL)
+ return;
+
+ cogl_buffer_set_update_hint (buffer, COGL_BUFFER_UPDATE_HINT_DYNAMIC);
+
+ data = cogl_buffer_map (buffer,
+ COGL_BUFFER_ACCESS_READ_WRITE,
+ COGL_BUFFER_MAP_HINT_DISCARD);
+
+ if (data != NULL)
+ {
+ int bitmap_stride = cogl_bitmap_get_rowstride (priv->buffer);
+
+ surface = cairo_image_surface_create_for_data (data,
+ CAIRO_FORMAT_ARGB32,
+ priv->width,
+ priv->height,
+ bitmap_stride);
+ mapped_buffer = TRUE;
+ }
+ else
+ {
+ surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
+ priv->width,
+ priv->height);
+
+ mapped_buffer = FALSE;
+ }
+
+ self->priv->cr = cr = cairo_create (surface);
+
+ g_signal_emit (self, canvas_signals[DRAW], 0, cr, &res);
+
+ self->priv->cr = NULL;
+ cairo_destroy (cr);
+
+ if (mapped_buffer)
+ cogl_buffer_unmap (buffer);
+ else
+ {
+ int size = cairo_image_surface_get_stride (surface) * priv->height;
+ cogl_buffer_set_data (buffer,
+ 0,
+ cairo_image_surface_get_data (surface),
+ size);
+ }
+
+ cairo_surface_destroy (surface);
+}
+
+static void
+clutter_canvas_invalidate (ClutterContent *content)
+{
+ ClutterCanvas *self = CLUTTER_CANVAS (content);
+ ClutterCanvasPrivate *priv = self->priv;
+
+ if (priv->buffer != NULL)
+ {
+ cogl_object_unref (priv->buffer);
+ priv->buffer = NULL;
+ }
+
+ if (priv->width < 0 || priv->height < 0)
+ return;
+
+ clutter_canvas_emit_draw (self);
+}
+
+static gboolean
+clutter_canvas_get_preferred_size (ClutterContent *content,
+ gfloat *width,
+ gfloat *height)
+{
+ ClutterCanvasPrivate *priv = CLUTTER_CANVAS (content)->priv;
+
+ if (priv->width < 0 || priv->height < 0)
+ return FALSE;
+
+ if (width != NULL)
+ *width = priv->width;
+
+ if (height != NULL)
+ *height = priv->height;
+
+ return TRUE;
+}
+
+static void
+clutter_content_iface_init (ClutterContentIface *iface)
+{
+ iface->invalidate = clutter_canvas_invalidate;
+ iface->paint_content = clutter_canvas_paint_content;
+ iface->get_preferred_size = clutter_canvas_get_preferred_size;
+}
+
+/**
+ * clutter_canvas_new:
+ *
+ * Creates a new instance of #ClutterCanvas.
+ *
+ * You should call clutter_canvas_set_size() to set the size of the canvas.
+ *
+ * You should call clutter_content_invalidate() every time you wish to
+ * draw the contents of the canvas.
+ *
+ * Return value: (transfer full): The newly allocated instance of
+ * #ClutterCanvas. Use g_object_unref() when done.
+ *
+ * Since: 1.10
+ */
+ClutterContent *
+clutter_canvas_new (void)
+{
+ return g_object_new (CLUTTER_TYPE_CANVAS, NULL);
+}
+
+/**
+ * clutter_canvas_set_size:
+ * @canvas: a #ClutterCanvas
+ * @width: the width of the canvas, in pixels
+ * @height: the height of the canvas, in pixels
+ *
+ * Sets the size of the @canvas.
+ *
+ * This function will cause the @canvas to be invalidated.
+ *
+ * Since: 1.10
+ */
+void
+clutter_canvas_set_size (ClutterCanvas *canvas,
+ int width,
+ int height)
+{
+ GObject *obj;
+ gboolean width_changed = FALSE, height_changed = FALSE;
+
+ g_return_if_fail (CLUTTER_IS_CANVAS (canvas));
+ g_return_if_fail (width >= -1 && height >= -1);
+
+ obj = G_OBJECT (canvas);
+
+ g_object_freeze_notify (obj);
+
+ if (canvas->priv->width != width)
+ {
+ canvas->priv->width = width;
+ width_changed = TRUE;
+
+ g_object_notify_by_pspec (obj, obj_props[PROP_WIDTH]);
+ }
+
+ if (canvas->priv->height != height)
+ {
+ canvas->priv->height = height;
+ height_changed = TRUE;
+
+ g_object_notify_by_pspec (obj, obj_props[PROP_HEIGHT]);
+ }
+
+ if (width_changed || height_changed)
+ clutter_content_invalidate (CLUTTER_CONTENT (canvas));
+
+ g_object_thaw_notify (obj);
+}
diff --git a/clutter/clutter-canvas.h b/clutter/clutter-canvas.h
new file mode 100644
index 000000000..02ed0d6bb
--- /dev/null
+++ b/clutter/clutter-canvas.h
@@ -0,0 +1,91 @@
+/*
+ * Clutter.
+ *
+ * An OpenGL based 'interactive canvas' library.
+ *
+ * Copyright (C) 2012 Intel Corporation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * Author:
+ * Emmanuele Bassi
+ */
+
+#ifndef __CLUTTER_CANVAS_H__
+#define __CLUTTER_CANVAS_H__
+
+#include
+
+G_BEGIN_DECLS
+
+#define CLUTTER_TYPE_CANVAS (clutter_canvas_get_type ())
+#define CLUTTER_CANVAS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CLUTTER_TYPE_CANVAS, ClutterCanvas))
+#define CLUTTER_IS_CANVAS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CLUTTER_TYPE_CANVAS))
+#define CLUTTER_CANVAS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CLUTTER_TYPE_CANVAS, ClutterCanvasClass))
+#define CLUTTER_IS_CANVAS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CLUTTER_TYPE_CANVAS))
+#define CLUTTER_CANVAS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CLUTTER_TYPE_CANVAS, ClutterCanvasClass))
+
+typedef struct _ClutterCanvas ClutterCanvas;
+typedef struct _ClutterCanvasPrivate ClutterCanvasPrivate;
+typedef struct _ClutterCanvasClass ClutterCanvasClass;
+
+/**
+ * ClutterCanvas:
+ *
+ * The ClutterCanvas structure contains
+ * private data and should only be accessed using the provided
+ * API.
+ *
+ * Since: 1.10
+ */
+struct _ClutterCanvas
+{
+ /*< private >*/
+ GObject parent_instance;
+
+ ClutterCanvasPrivate *priv;
+};
+
+/**
+ * ClutterCanvasClass:
+ * @draw: class handler for the #ClutterCanvas::draw signal
+ *
+ * The ClutterCanvasClass structure contains
+ * private data.
+ *
+ * Since: 1.10
+ */
+struct _ClutterCanvasClass
+{
+ /*< private >*/
+ GObjectClass parent_class;
+
+ /*< public >*/
+ gboolean (* draw) (ClutterCanvas *canvas,
+ cairo_t *cr);
+
+ /*< private >*/
+ gpointer _padding[16];
+};
+
+GType clutter_canvas_get_type (void) G_GNUC_CONST;
+
+ClutterContent * clutter_canvas_new (void);
+void clutter_canvas_set_size (ClutterCanvas *canvas,
+ int width,
+ int height);
+
+G_END_DECLS
+
+#endif /* __CLUTTER_CANVAS_H__ */
diff --git a/clutter/clutter-macros.h b/clutter/clutter-macros.h
index 2e70a6b8e..50eb021e2 100644
--- a/clutter/clutter-macros.h
+++ b/clutter/clutter-macros.h
@@ -248,4 +248,24 @@
# define CLUTTER_AVAILABLE_IN_1_10
#endif
+/**
+ * CLUTTER_CAIRO_FORMAT_ARGB32:
+ *
+ * The #CoglPixelFormat to be used when uploading image data from
+ * and to a Cairo image surface using %CAIRO_FORMAT_ARGB32 and
+ * %CAIRO_FORMAT_RGB24 as #cairo_format_t.
+ *
+ * Since: 1.8
+ */
+
+/* Cairo stores the data in native byte order as ARGB but Cogl's pixel
+ * formats specify the actual byte order. Therefore we need to use a
+ * different format depending on the architecture
+ */
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+#define CLUTTER_CAIRO_FORMAT_ARGB32 (COGL_PIXEL_FORMAT_BGRA_8888_PRE)
+#else
+#define CLUTTER_CAIRO_FORMAT_ARGB32 (COGL_PIXEL_FORMAT_ARGB_8888_PRE)
+#endif
+
#endif /* __CLUTTER_MACROS_H__ */
diff --git a/clutter/clutter.h b/clutter/clutter.h
index 070691bc7..2a412df2f 100644
--- a/clutter/clutter.h
+++ b/clutter/clutter.h
@@ -47,6 +47,7 @@
#include "clutter-box-layout.h"
#include "clutter-brightness-contrast-effect.h"
#include "clutter-cairo-texture.h"
+#include "clutter-canvas.h"
#include "clutter-child-meta.h"
#include "clutter-click-action.h"
#include "clutter-clone.h"
diff --git a/clutter/clutter.symbols b/clutter/clutter.symbols
index e55648fdf..4ac15051b 100644
--- a/clutter/clutter.symbols
+++ b/clutter/clutter.symbols
@@ -482,6 +482,9 @@ clutter_brightness_contrast_effect_set_brightness_full
clutter_brightness_contrast_effect_set_brightness
clutter_brightness_contrast_effect_set_contrast_full
clutter_brightness_contrast_effect_set_contrast
+clutter_canvas_get_type
+clutter_canvas_new
+clutter_canvas_set_size
clutter_cairo_set_source_color
clutter_cairo_texture_clear
clutter_cairo_texture_create
diff --git a/doc/reference/clutter/clutter-docs.xml.in b/doc/reference/clutter/clutter-docs.xml.in
index bc5a7fc26..32ccda223 100644
--- a/doc/reference/clutter/clutter-docs.xml.in
+++ b/doc/reference/clutter/clutter-docs.xml.in
@@ -121,6 +121,12 @@
+
+ Content
+
+
+
+
Paint Objects
diff --git a/doc/reference/clutter/clutter-sections.txt b/doc/reference/clutter/clutter-sections.txt
index 77ae59b1c..0c2f91eea 100644
--- a/doc/reference/clutter/clutter-sections.txt
+++ b/doc/reference/clutter/clutter-sections.txt
@@ -3132,3 +3132,20 @@ clutter_text_node_get_type
clutter_clip_node_get_type
clutter_layer_node_get_type
+
+
+clutter-canvas
+ClutterCanvas
+ClutterCanvasClass
+clutter_canvas_new
+clutter_canvas_set_size
+
+CLUTTER_TYPE_CANVAS
+CLUTTER_CANVAS
+CLUTTER_CANVAS_CLASS
+CLUTTER_IS_CANVAS
+CLUTTER_IS_CANVAS_CLASS
+
+ClutterCanvasPrivate
+clutter_canvas_get_type
+
diff --git a/doc/reference/clutter/clutter.types b/doc/reference/clutter/clutter.types
index 58d5b5f85..d81be1214 100644
--- a/doc/reference/clutter/clutter.types
+++ b/doc/reference/clutter/clutter.types
@@ -22,6 +22,7 @@ clutter_blur_effect_get_type
clutter_box_get_type
clutter_box_layout_get_type
clutter_brightness_contrast_effect_get_type
+clutter_canvas_get_type
clutter_cairo_texture_get_type
clutter_child_meta_get_type
clutter_click_action_get_type
diff --git a/tests/interactive/Makefile.am b/tests/interactive/Makefile.am
index b0523e5c1..2656e581c 100644
--- a/tests/interactive/Makefile.am
+++ b/tests/interactive/Makefile.am
@@ -57,7 +57,8 @@ UNIT_TESTS = \
test-devices.c \
test-actor.c \
test-transitions.c \
- test-content.c
+ test-content.c \
+ test-canvas.c
if X11_TESTS
UNIT_TESTS += test-pixmap.c
diff --git a/tests/interactive/test-canvas.c b/tests/interactive/test-canvas.c
new file mode 100644
index 000000000..61038ef31
--- /dev/null
+++ b/tests/interactive/test-canvas.c
@@ -0,0 +1,135 @@
+#include
+#include
+#include
+#include
+
+static gboolean
+draw_clock (ClutterCanvas *canvas,
+ cairo_t *cr)
+{
+ float width, height;
+ GDateTime *now;
+ float hours, minutes, seconds;
+ ClutterColor color;
+
+ /* get the current time and compute the angles */
+ now = g_date_time_new_now_local ();
+ seconds = g_date_time_get_second (now) * G_PI / 30;
+ minutes = g_date_time_get_minute (now) * G_PI / 30;
+ hours = g_date_time_get_hour (now) * G_PI / 6;
+
+ cairo_save (cr);
+
+ /* clear the contents of the canvas, to avoid painting
+ * over the previous frame
+ */
+ cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
+ cairo_paint (cr);
+
+ cairo_restore (cr);
+
+ cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
+
+ /* scale the modelview to the size of the surface */
+ clutter_content_get_preferred_size (CLUTTER_CONTENT (canvas),
+ &width,
+ &height);
+ cairo_scale (cr, width, height);
+
+ cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
+ cairo_set_line_width (cr, 0.1);
+
+ /* the black rail that holds the seconds indicator */
+ clutter_cairo_set_source_color (cr, CLUTTER_COLOR_Black);
+ cairo_translate (cr, 0.5, 0.5);
+ cairo_arc (cr, 0, 0, 0.4, 0, G_PI * 2);
+ cairo_stroke (cr);
+
+ /* the seconds indicator */
+ color = *CLUTTER_COLOR_White;
+ color.alpha = 128;
+ clutter_cairo_set_source_color (cr, &color);
+ cairo_move_to (cr, 0, 0);
+ cairo_arc (cr, sinf (seconds) * 0.4, - cosf (seconds) * 0.4, 0.05, 0, G_PI * 2);
+ cairo_fill (cr);
+
+ /* the minutes hand */
+ color = *CLUTTER_COLOR_DarkChameleon;
+ color.alpha = 196;
+ clutter_cairo_set_source_color (cr, &color);
+ cairo_move_to (cr, 0, 0);
+ cairo_line_to (cr, sinf (minutes) * 0.4, -cosf (minutes) * 0.4);
+ cairo_stroke (cr);
+
+ /* the hours hand */
+ cairo_move_to (cr, 0, 0);
+ cairo_line_to (cr, sinf (hours) * 0.2, -cosf (hours) * 0.2);
+ cairo_stroke (cr);
+
+ g_date_time_unref (now);
+
+ /* we're done drawing */
+ return TRUE;
+}
+
+static gboolean
+invalidate_clock (gpointer data_)
+{
+ /* invalidate the contents of the canvas */
+ clutter_content_invalidate (data_);
+
+ /* keep the timeout source */
+ return TRUE;
+}
+
+G_MODULE_EXPORT int
+test_canvas_main (int argc, char *argv[])
+{
+ ClutterActor *stage, *actor;
+ ClutterContent *canvas;
+
+ /* initialize Clutter */
+ if (clutter_init (&argc, &argv) != CLUTTER_INIT_SUCCESS)
+ return EXIT_FAILURE;
+
+ /* create a resizable stage */
+ stage = clutter_stage_new ();
+ clutter_stage_set_title (CLUTTER_STAGE (stage), "2D Clock");
+ clutter_stage_set_user_resizable (CLUTTER_STAGE (stage), TRUE);
+ clutter_actor_set_background_color (stage, CLUTTER_COLOR_LightSkyBlue);
+ clutter_actor_set_size (stage, 300, 300);
+ clutter_actor_show (stage);
+
+ /* our 2D canvas, courtesy of Cairo */
+ canvas = clutter_canvas_new ();
+ clutter_canvas_set_size (CLUTTER_CANVAS (canvas), 300, 300);
+
+ actor = clutter_actor_new ();
+ clutter_actor_set_content (actor, canvas);
+ clutter_actor_add_child (stage, actor);
+
+ /* bind the size of the actor to that of the stage */
+ clutter_actor_add_constraint (actor, clutter_bind_constraint_new (stage, CLUTTER_BIND_SIZE, 0));
+
+ /* quit on destroy */
+ g_signal_connect (stage, "destroy", G_CALLBACK (clutter_main_quit), NULL);
+
+ /* connect our drawing code */
+ g_signal_connect (canvas, "draw", G_CALLBACK (draw_clock), NULL);
+
+ /* invalidate the canvas, so that we can draw before the main loop starts */
+ clutter_content_invalidate (canvas);
+
+ /* set up a timer that invalidates the canvas every second */
+ clutter_threads_add_timeout (1000, invalidate_clock, canvas);
+
+ clutter_main ();
+
+ return EXIT_SUCCESS;
+}
+
+G_MODULE_EXPORT const char *
+test_canvas_describe (void)
+{
+ return "Simple 2D clock using the Canvas content";
+}