Commit 2f445682 authored by Emmanuele Bassi's avatar Emmanuele Bassi

cairo-texture: Use signal-based drawing

The current "create context/draw/destroy context" pattern presents
various problems. The first issue is that it defers memory management to
the caller of the create() or create_region() methods, which makes
bookkeeping of the cairo_t* harder for language bindings and third party
libraries. The second issue is that, while it's easier for
draw-and-forget texturs, this API is needlessly complicated for contents
that have to change programmatically - and it introduces constraints
like calling the drawing code explicitly after a surface resize (e.g.
inside an allocate() implementation).

By using a signal-based approach we can make the CairoTexture actor
behave like other actors, and like other libraries using Cairo as their
2D drawing API.

The semantics of the newly-introduced ::draw signal are the same as the
one used by GTK+:

  - the signal is emitted on invalidation;
  - the cairo_t* context is owned by the actor;
  - it is safe to have multiple callbacks attached to the same
    signal, to allow composition;
  - the cairo_t* is already clipped to the invalidated area, so
    that Cairo can discard geometry immediately before we upload
    the texture data.

There are possible future improvements, like coalescing multiple
invalidations inside regions, and performing clipped draws during
the paint cycle; we could even perform clipped redraws if we know the
extent of the invalidated area.
parent dcad2712
This diff is collapsed.
......@@ -102,21 +102,26 @@ struct _ClutterCairoTextureClass
guint width,
guint height);
gboolean (* draw) (ClutterCairoTexture *texture,
cairo_t *cr);
/*< private >*/
void (*_clutter_cairo_2) (void);
void (*_clutter_cairo_3) (void);
void (*_clutter_cairo_4) (void);
GType clutter_cairo_texture_get_type (void) G_GNUC_CONST;
ClutterActor *clutter_cairo_texture_new (guint width,
ClutterActor * clutter_cairo_texture_new (guint width,
guint height);
cairo_t * clutter_cairo_texture_create_region (ClutterCairoTexture *self,
gint x_offset,
gint y_offset,
gint width,
gint height);
cairo_t * clutter_cairo_texture_create (ClutterCairoTexture *self);
void clutter_cairo_texture_set_surface_size (ClutterCairoTexture *self,
guint width,
guint height);
......@@ -126,6 +131,10 @@ void clutter_cairo_texture_get_surface_size (ClutterCairoTexture *self,
void clutter_cairo_texture_clear (ClutterCairoTexture *self);
void clutter_cairo_texture_invalidate_rectangle (ClutterCairoTexture *self,
cairo_rectangle_int_t *rect);
void clutter_cairo_texture_invalidate (ClutterCairoTexture *self);
void clutter_cairo_set_source_color (cairo_t *cr,
const ClutterColor *color);
......@@ -1760,6 +1760,10 @@ clutter_cairo_texture_get_surface_size
......@@ -43,6 +43,7 @@ UNIT_TESTS = \
test-binding-pool.c \
test-text.c \
test-text-field.c \
test-cairo-clock.c \
test-cairo-flowers.c \
test-cogl-vertex-buffer.c \
test-bin-layout.c \
#include <stdlib.h>
#include <math.h>
#include <cairo.h>
#include <clutter/clutter.h>
static gboolean
draw_clock (ClutterCairoTexture *canvas,
cairo_t *cr)
guint width, height;
GDateTime *now;
float hours, minutes, seconds;
/* 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;
/* clear the contents of the canvas, to avoid painting
* over the previous frame
clutter_cairo_texture_clear (canvas);
/* scale the modelview to the size of the surface */
clutter_cairo_texture_get_surface_size (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 */
clutter_cairo_set_source_color (cr, CLUTTER_COLOR_White);
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 */
clutter_cairo_set_source_color (cr, CLUTTER_COLOR_DarkChameleon);
cairo_set_source_rgba (cr, 0.2, 0.2, 1, 0.6);
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_cairo_texture_invalidate (data_);
/* keep the timeout source */
return TRUE;
test_cairo_clock_main (int argc, char *argv[])
ClutterActor *stage, *canvas;
/* initialize Clutter */
if (clutter_init (&argc, &argv) != CLUTTER_INIT_SUCCESS)
/* create a fixed size stage */
stage = clutter_stage_new ();
clutter_stage_set_title (CLUTTER_STAGE (stage), "2D Clock");
clutter_stage_set_color (CLUTTER_STAGE (stage), CLUTTER_COLOR_LightSkyBlue);
clutter_actor_set_size (stage, 300, 300);
clutter_actor_show (stage);
/* our 2D canvas, courtesy of Cairo */
canvas = clutter_cairo_texture_new (300, 300);
clutter_container_add_actor (CLUTTER_CONTAINER (stage), canvas);
/* 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_cairo_texture_invalidate (CLUTTER_CAIRO_TEXTURE (canvas));
/* set up a timer that invalidates the canvas every second */
clutter_threads_add_timeout (1000, invalidate_clock, canvas);
clutter_main ();
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment