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"; +}