1 /*
2 * Clutter
3 *
4 * An OpenGL based 'interactive canvas' library.
5 *
6 * Authored By: Emmanuele Bassi <ebassi@linux.intel.com>
7 * Matthew Allum <mallum@o-hand.com>
8 * Chris Lord <chris@o-hand.com>
9 * Iain Holmes <iain@o-hand.com>
10 * Neil Roberts <neil@linux.intel.com>
11 *
12 * Copyright (C) 2008, 2009, 2010, 2011 Intel Corporation.
13 *
14 * This library is free software; you can redistribute it and/or
15 * modify it under the terms of the GNU Lesser General Public
16 * License as published by the Free Software Foundation; either
17 * version 2 of the License, or (at your option) any later version.
18 *
19 * This library is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22 * Lesser General Public License for more details.
23 *
24 * You should have received a copy of the GNU Lesser General Public
25 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
26 */
27
28 /**
29 * SECTION:clutter-cairo-texture
30 * @short_description: Texture with Cairo integration
31 *
32 * #ClutterCairoTexture is a #ClutterTexture that displays the contents
33 * of a Cairo context. The #ClutterCairoTexture actor will create a
34 * Cairo image surface which will then be uploaded to a GL texture when
35 * needed.
36 *
37 * Since #ClutterCairoTexture uses a Cairo image surface
38 * internally all the drawing operations will be performed in
39 * software and not using hardware acceleration. This can lead to
40 * performance degradation if the contents of the texture change
41 * frequently.
42 *
43 * In order to use a #ClutterCairoTexture you should connect to the
44 * #ClutterCairoTexture::draw signal; the signal is emitted each time
45 * the #ClutterCairoTexture has been told to invalidate its contents,
46 * by using clutter_cairo_texture_invalidate_rectangle() or its
47 * sister function, clutter_cairo_texture_invalidate().
48 *
49 * Each callback to the #ClutterCairoTexture::draw signal will receive
50 * a #cairo_t context which can be used for drawing; the Cairo context
51 * is owned by the #ClutterCairoTexture and should not be destroyed
52 * explicitly.
53 *
54 * #ClutterCairoTexture is available since Clutter 1.0.
55 *
56 * #ClutterCairoTexture is deprecated since Clutter 1.12. You should
57 * use #ClutterCanvas instead.
58 */
59
60 #ifdef HAVE_CONFIG_H
61 #include "config.h"
62 #endif
63
64 #include <math.h>
65
66 #include <cairo-gobject.h>
67
68 #define CLUTTER_DISABLE_DEPRECATION_WARNINGS
69 #include "deprecated/clutter-texture.h"
70 #include "deprecated/clutter-cairo-texture.h"
71
72 #include "clutter-cairo-texture.h"
73
74 #include "clutter-actor-private.h"
75 #include "clutter-cairo.h"
76 #include "clutter-color.h"
77 #include "clutter-debug.h"
78 #include "clutter-marshal.h"
79 #include "clutter-private.h"
80
81 struct _ClutterCairoTexturePrivate
82 {
83 cairo_surface_t *cr_surface;
84
85 guint surface_width;
86 guint surface_height;
87
88 cairo_t *cr_context;
89
90 guint auto_resize : 1;
91 };
92
93 enum
94 {
95 PROP_0,
96
97 PROP_SURFACE_WIDTH,
98 PROP_SURFACE_HEIGHT,
99 PROP_AUTO_RESIZE,
100
101 PROP_LAST
102 };
103
104 enum
105 {
106 CREATE_SURFACE,
107 DRAW,
108
109 LAST_SIGNAL
110 };
111
112 static GParamSpec *obj_props[PROP_LAST] = { NULL, };
113
114 static guint cairo_signals[LAST_SIGNAL] = { 0, };
115
116 G_DEFINE_TYPE_WITH_PRIVATE (ClutterCairoTexture,
117 clutter_cairo_texture,
118 CLUTTER_TYPE_TEXTURE)
119
120 #ifdef CLUTTER_ENABLE_DEBUG
121 #define clutter_warn_if_paint_fail(obj) G_STMT_START { \
122 if (CLUTTER_ACTOR_IN_PAINT (obj)) { \
123 g_warning ("%s should not be called during the paint sequence " \
124 "of a ClutterCairoTexture as it will likely cause " \
125 "performance issues.", G_STRFUNC); \
126 } } G_STMT_END
127 #else
128 #define clutter_warn_if_paint_fail(obj) /* void */
129 #endif /* CLUTTER_ENABLE_DEBUG */
130
131 typedef struct {
132 ClutterCairoTexture *texture;
133
134 cairo_rectangle_int_t rect;
135
136 guint is_clipped : 1;
137 } DrawContext;
138
139 static const cairo_user_data_key_t clutter_cairo_texture_context_key;
140
141 static DrawContext *
draw_context_create(ClutterCairoTexture * texture)142 draw_context_create (ClutterCairoTexture *texture)
143 {
144 DrawContext *context = g_slice_new0 (DrawContext);
145
146 context->texture = g_object_ref (texture);
147
148 return context;
149 }
150
151 static void
draw_context_destroy(gpointer data)152 draw_context_destroy (gpointer data)
153 {
154 if (G_LIKELY (data != NULL))
155 {
156 DrawContext *context = data;
157
158 g_object_unref (context->texture);
159
160 g_slice_free (DrawContext, data);
161 }
162 }
163
164 static void
clutter_cairo_texture_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)165 clutter_cairo_texture_set_property (GObject *object,
166 guint prop_id,
167 const GValue *value,
168 GParamSpec *pspec)
169 {
170 ClutterCairoTexturePrivate *priv;
171
172 priv = CLUTTER_CAIRO_TEXTURE (object)->priv;
173
174 switch (prop_id)
175 {
176 case PROP_SURFACE_WIDTH:
177 /* we perform the resize on notify to coalesce separate
178 * surface-width/surface-height property set
179 */
180 priv->surface_width = g_value_get_uint (value);
181 break;
182
183 case PROP_SURFACE_HEIGHT:
184 priv->surface_height = g_value_get_uint (value);
185 break;
186
187 case PROP_AUTO_RESIZE:
188 clutter_cairo_texture_set_auto_resize (CLUTTER_CAIRO_TEXTURE (object),
189 g_value_get_boolean (value));
190 break;
191
192 default:
193 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
194 break;
195 }
196 }
197
198 static void
clutter_cairo_texture_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)199 clutter_cairo_texture_get_property (GObject *object,
200 guint prop_id,
201 GValue *value,
202 GParamSpec *pspec)
203 {
204 ClutterCairoTexturePrivate *priv;
205
206 priv = CLUTTER_CAIRO_TEXTURE (object)->priv;
207
208 switch (prop_id)
209 {
210 case PROP_SURFACE_WIDTH:
211 g_value_set_uint (value, priv->surface_width);
212 break;
213
214 case PROP_SURFACE_HEIGHT:
215 g_value_set_uint (value, priv->surface_height);
216 break;
217
218 case PROP_AUTO_RESIZE:
219 g_value_set_boolean (value, priv->auto_resize);
220 break;
221
222 default:
223 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
224 break;
225 }
226 }
227
228 static void
clutter_cairo_texture_finalize(GObject * object)229 clutter_cairo_texture_finalize (GObject *object)
230 {
231 ClutterCairoTexturePrivate *priv = CLUTTER_CAIRO_TEXTURE (object)->priv;
232
233 if (priv->cr_surface != NULL)
234 {
235 cairo_surface_t *surface = priv->cr_surface;
236
237 priv->cr_surface = NULL;
238
239 cairo_surface_finish (surface);
240 cairo_surface_destroy (surface);
241 }
242
243 G_OBJECT_CLASS (clutter_cairo_texture_parent_class)->finalize (object);
244 }
245
246 static cairo_surface_t *
get_surface(ClutterCairoTexture * self)247 get_surface (ClutterCairoTexture *self)
248 {
249 ClutterCairoTexturePrivate *priv = self->priv;
250
251 if (priv->cr_surface == NULL)
252 {
253 g_signal_emit (self, cairo_signals[CREATE_SURFACE], 0,
254 priv->surface_width,
255 priv->surface_height,
256 &priv->cr_surface);
257 }
258
259 return priv->cr_surface;
260 }
261
262 static void
clutter_cairo_texture_context_destroy(void * data)263 clutter_cairo_texture_context_destroy (void *data)
264 {
265 DrawContext *ctxt = data;
266 ClutterCairoTexture *cairo = ctxt->texture;
267 ClutterCairoTexturePrivate *priv = cairo->priv;
268 guint8 *cairo_data;
269 gint cairo_width, cairo_height, cairo_stride;
270 gint surface_width, surface_height;
271 CoglHandle cogl_texture;
272
273 if (priv->cr_surface == NULL)
274 {
275 /* the surface went away before we could use it */
276 draw_context_destroy (ctxt);
277 return;
278 }
279
280 /* for any other surface type, we presume that there exists a native
281 * communication between Cairo and GL that is triggered by cairo_destroy().
282 *
283 * for instance, cairo-drm will flush the outstanding modifications to the
284 * surface upon context destruction and so the texture is automatically
285 * updated.
286 */
287 if (cairo_surface_get_type (priv->cr_surface) != CAIRO_SURFACE_TYPE_IMAGE)
288 goto out;
289
290 surface_width = cairo_image_surface_get_width (priv->cr_surface);
291 surface_height = cairo_image_surface_get_height (priv->cr_surface);
292
293 cairo_width = MIN (ctxt->rect.width, surface_width);
294 cairo_height = MIN (ctxt->rect.height, surface_height);
295
296 cogl_texture = clutter_texture_get_cogl_texture (CLUTTER_TEXTURE (cairo));
297 if (cairo_width == 0 ||
298 cairo_height == 0 ||
299 cogl_texture == COGL_INVALID_HANDLE)
300 {
301 draw_context_destroy (ctxt);
302 return;
303 }
304
305 cairo_stride = cairo_image_surface_get_stride (priv->cr_surface);
306 cairo_data = cairo_image_surface_get_data (priv->cr_surface);
307 cairo_data += cairo_stride * ctxt->rect.y;
308 cairo_data += 4 * ctxt->rect.x;
309
310 cogl_texture_set_region (cogl_texture,
311 0, 0,
312 ctxt->rect.x, ctxt->rect.y,
313 cairo_width, cairo_height,
314 cairo_width, cairo_height,
315 CLUTTER_CAIRO_FORMAT_ARGB32,
316 cairo_stride,
317 cairo_data);
318
319 out:
320 draw_context_destroy (ctxt);
321 clutter_actor_queue_redraw (CLUTTER_ACTOR (cairo));
322 }
323
324 static inline void
clutter_cairo_texture_emit_draw(ClutterCairoTexture * self,DrawContext * ctxt)325 clutter_cairo_texture_emit_draw (ClutterCairoTexture *self,
326 DrawContext *ctxt)
327 {
328 gboolean result;
329 cairo_t *cr;
330
331 /* 0x0 surfaces don't need a ::draw */
332 if (self->priv->surface_width == 0 ||
333 self->priv->surface_height == 0)
334 return;
335
336 /* if the size is !0 then we must have a surface */
337 g_assert (self->priv->cr_surface != NULL);
338
339 cr = cairo_create (self->priv->cr_surface);
340
341 if (ctxt->is_clipped)
342 {
343 cairo_rectangle (cr,
344 ctxt->rect.x,
345 ctxt->rect.y,
346 ctxt->rect.width,
347 ctxt->rect.height);
348 cairo_clip (cr);
349 }
350
351 /* store the cairo_t as a guard */
352 self->priv->cr_context = cr;
353
354 g_signal_emit (self, cairo_signals[DRAW], 0, cr, &result);
355
356 self->priv->cr_context = NULL;
357
358 clutter_cairo_texture_context_destroy (ctxt);
359
360 cairo_destroy (cr);
361 }
362
363 static inline void
clutter_cairo_texture_surface_resize_internal(ClutterCairoTexture * cairo)364 clutter_cairo_texture_surface_resize_internal (ClutterCairoTexture *cairo)
365 {
366 ClutterCairoTexturePrivate *priv = cairo->priv;
367
368 if (priv->cr_surface != NULL)
369 {
370 cairo_surface_t *surface = priv->cr_surface;
371
372 /* if the surface is an image one, and the size is already the
373 * same, then we don't need to do anything
374 */
375 if (cairo_surface_get_type (surface) != CAIRO_SURFACE_TYPE_IMAGE)
376 {
377 gint surface_width = cairo_image_surface_get_width (surface);
378 gint surface_height = cairo_image_surface_get_height (surface);
379
380 if (priv->surface_width == surface_width &&
381 priv->surface_height == surface_height)
382 return;
383 }
384
385 cairo_surface_finish (surface);
386 cairo_surface_destroy (surface);
387 priv->cr_surface = NULL;
388 }
389
390 if (priv->surface_width == 0 ||
391 priv->surface_height == 0)
392 return;
393
394 g_signal_emit (cairo, cairo_signals[CREATE_SURFACE], 0,
395 priv->surface_width,
396 priv->surface_height,
397 &priv->cr_surface);
398 }
399
400 static void
clutter_cairo_texture_notify(GObject * object,GParamSpec * pspec)401 clutter_cairo_texture_notify (GObject *object,
402 GParamSpec *pspec)
403 {
404 /* When the surface width or height changes then resize the cairo
405 surface. This is done here instead of directly in set_property so
406 that if both the width and height properties are set using a
407 single call to g_object_set then the surface will only be resized
408 once because the notifications will be frozen in between */
409
410 if (obj_props[PROP_SURFACE_WIDTH]->name == pspec->name ||
411 obj_props[PROP_SURFACE_HEIGHT]->name == pspec->name)
412 {
413 ClutterCairoTexture *cairo = CLUTTER_CAIRO_TEXTURE (object);
414
415 clutter_cairo_texture_surface_resize_internal (cairo);
416 }
417
418 if (G_OBJECT_CLASS (clutter_cairo_texture_parent_class)->notify)
419 G_OBJECT_CLASS (clutter_cairo_texture_parent_class)->notify (object, pspec);
420 }
421
422 static void
clutter_cairo_texture_get_preferred_width(ClutterActor * actor,gfloat for_height,gfloat * min_width,gfloat * natural_width)423 clutter_cairo_texture_get_preferred_width (ClutterActor *actor,
424 gfloat for_height,
425 gfloat *min_width,
426 gfloat *natural_width)
427 {
428 ClutterCairoTexturePrivate *priv = CLUTTER_CAIRO_TEXTURE (actor)->priv;
429
430 if (min_width)
431 *min_width = 0;
432
433 if (natural_width)
434 *natural_width = (gfloat) priv->surface_width;
435 }
436
437 static void
clutter_cairo_texture_get_preferred_height(ClutterActor * actor,gfloat for_width,gfloat * min_height,gfloat * natural_height)438 clutter_cairo_texture_get_preferred_height (ClutterActor *actor,
439 gfloat for_width,
440 gfloat *min_height,
441 gfloat *natural_height)
442 {
443 ClutterCairoTexturePrivate *priv = CLUTTER_CAIRO_TEXTURE (actor)->priv;
444
445 if (min_height)
446 *min_height = 0;
447
448 if (natural_height)
449 *natural_height = (gfloat) priv->surface_height;
450 }
451
452 static void
clutter_cairo_texture_allocate(ClutterActor * self,const ClutterActorBox * allocation,ClutterAllocationFlags flags)453 clutter_cairo_texture_allocate (ClutterActor *self,
454 const ClutterActorBox *allocation,
455 ClutterAllocationFlags flags)
456 {
457 ClutterCairoTexturePrivate *priv = CLUTTER_CAIRO_TEXTURE (self)->priv;
458 ClutterActorClass *parent_class;
459
460 parent_class = CLUTTER_ACTOR_CLASS (clutter_cairo_texture_parent_class);
461 parent_class->allocate (self, allocation, flags);
462
463 if (priv->auto_resize)
464 {
465 ClutterCairoTexture *texture = CLUTTER_CAIRO_TEXTURE (self);
466 gfloat width, height;
467
468 clutter_actor_box_get_size (allocation, &width, &height);
469
470 priv->surface_width = ceilf (width);
471 priv->surface_height = ceilf (height);
472
473 clutter_cairo_texture_surface_resize_internal (texture);
474 clutter_cairo_texture_invalidate (texture);
475 }
476 }
477
478 static gboolean
clutter_cairo_texture_get_paint_volume(ClutterActor * self,ClutterPaintVolume * volume)479 clutter_cairo_texture_get_paint_volume (ClutterActor *self,
480 ClutterPaintVolume *volume)
481 {
482 return _clutter_actor_set_default_paint_volume (self,
483 CLUTTER_TYPE_CAIRO_TEXTURE,
484 volume);
485 }
486
487 static cairo_surface_t *
clutter_cairo_texture_create_surface(ClutterCairoTexture * self,guint width,guint height)488 clutter_cairo_texture_create_surface (ClutterCairoTexture *self,
489 guint width,
490 guint height)
491 {
492 cairo_surface_t *surface;
493 guint cairo_stride;
494 guint8 *cairo_data;
495 CoglHandle cogl_texture;
496
497 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
498 width,
499 height);
500
501 cairo_stride = cairo_image_surface_get_stride (surface);
502 cairo_data = cairo_image_surface_get_data (surface);
503
504 /* create a backing Cogl texture */
505 cogl_texture = cogl_texture_new_from_data (width, height,
506 COGL_TEXTURE_NONE,
507 CLUTTER_CAIRO_FORMAT_ARGB32,
508 COGL_PIXEL_FORMAT_ANY,
509 cairo_stride,
510 cairo_data);
511 clutter_texture_set_cogl_texture (CLUTTER_TEXTURE (self), cogl_texture);
512 cogl_handle_unref (cogl_texture);
513
514 return surface;
515 }
516
517 static gboolean
create_surface_accum(GSignalInvocationHint * ihint,GValue * return_accu,const GValue * handler_return,gpointer data)518 create_surface_accum (GSignalInvocationHint *ihint,
519 GValue *return_accu,
520 const GValue *handler_return,
521 gpointer data)
522 {
523 g_value_copy (handler_return, return_accu);
524
525 /* stop on the first non-NULL return value */
526 return g_value_get_boxed (handler_return) == NULL;
527 }
528
529 static void
clutter_cairo_texture_draw_marshaller(GClosure * closure,GValue * return_value,guint n_param_values,const GValue * param_values,gpointer invocation_hint,gpointer marshal_data)530 clutter_cairo_texture_draw_marshaller (GClosure *closure,
531 GValue *return_value,
532 guint n_param_values,
533 const GValue *param_values,
534 gpointer invocation_hint,
535 gpointer marshal_data)
536 {
537 cairo_t *cr = g_value_get_boxed (¶m_values[1]);
538
539 cairo_save (cr);
540
541 _clutter_marshal_BOOLEAN__BOXED (closure,
542 return_value,
543 n_param_values,
544 param_values,
545 invocation_hint,
546 marshal_data);
547
548 cairo_restore (cr);
549 }
550
551 static void
clutter_cairo_texture_class_init(ClutterCairoTextureClass * klass)552 clutter_cairo_texture_class_init (ClutterCairoTextureClass *klass)
553 {
554 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
555 ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
556
557 gobject_class->finalize = clutter_cairo_texture_finalize;
558 gobject_class->set_property = clutter_cairo_texture_set_property;
559 gobject_class->get_property = clutter_cairo_texture_get_property;
560 gobject_class->notify = clutter_cairo_texture_notify;
561
562 actor_class->get_paint_volume =
563 clutter_cairo_texture_get_paint_volume;
564 actor_class->get_preferred_width =
565 clutter_cairo_texture_get_preferred_width;
566 actor_class->get_preferred_height =
567 clutter_cairo_texture_get_preferred_height;
568 actor_class->allocate =
569 clutter_cairo_texture_allocate;
570
571 klass->create_surface = clutter_cairo_texture_create_surface;
572
573 /**
574 * ClutterCairoTexture:surface-width:
575 *
576 * The width of the Cairo surface used by the #ClutterCairoTexture
577 * actor, in pixels.
578 *
579 * Since: 1.0
580 *
581 * Deprecated: 1.12
582 */
583 obj_props[PROP_SURFACE_WIDTH] =
584 g_param_spec_uint ("surface-width",
585 P_("Surface Width"),
586 P_("The width of the Cairo surface"),
587 0, G_MAXUINT,
588 0,
589 CLUTTER_PARAM_READWRITE |
590 G_PARAM_DEPRECATED);
591 /**
592 * ClutterCairoTexture:surface-height:
593 *
594 * The height of the Cairo surface used by the #ClutterCairoTexture
595 * actor, in pixels.
596 *
597 * Since: 1.0
598 *
599 * Deprecated: 1.12
600 */
601 obj_props[PROP_SURFACE_HEIGHT] =
602 g_param_spec_uint ("surface-height",
603 P_("Surface Height"),
604 P_("The height of the Cairo surface"),
605 0, G_MAXUINT,
606 0,
607 CLUTTER_PARAM_READWRITE |
608 G_PARAM_DEPRECATED);
609
610 /**
611 * ClutterCairoTexture:auto-resize:
612 *
613 * Controls whether the #ClutterCairoTexture should automatically
614 * resize the Cairo surface whenever the actor's allocation changes.
615 * If :auto-resize is set to %TRUE the surface contents will also
616 * be invalidated automatically.
617 *
618 * Since: 1.8
619 *
620 * Deprecated: 1.12
621 */
622 obj_props[PROP_AUTO_RESIZE] =
623 g_param_spec_boolean ("auto-resize",
624 P_("Auto Resize"),
625 P_("Whether the surface should match the allocation"),
626 FALSE,
627 CLUTTER_PARAM_READWRITE |
628 G_PARAM_DEPRECATED);
629
630 g_object_class_install_properties (gobject_class, PROP_LAST, obj_props);
631
632 /**
633 * ClutterCairoTexture::create-surface:
634 * @texture: the #ClutterCairoTexture that emitted the signal
635 * @width: the width of the surface to create
636 * @height: the height of the surface to create
637 *
638 * The ::create-surface signal is emitted when a #ClutterCairoTexture
639 * news its surface (re)created, which happens either when the Cairo
640 * context is created with clutter_cairo_texture_create() or
641 * clutter_cairo_texture_create_region(), or when the surface is resized
642 * through clutter_cairo_texture_set_surface_size().
643 *
644 * The first signal handler that returns a non-%NULL, valid surface will
645 * stop any further signal emission, and the returned surface will be
646 * the one used.
647 *
648 * Return value: the newly created #cairo_surface_t for the texture
649 *
650 * Since: 1.6
651 *
652 * Deprecated: 1.12
653 */
654 cairo_signals[CREATE_SURFACE] =
655 g_signal_new (I_("create-surface"),
656 G_TYPE_FROM_CLASS (klass),
657 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE,
658 G_STRUCT_OFFSET (ClutterCairoTextureClass, create_surface),
659 create_surface_accum, NULL,
660 _clutter_marshal_BOXED__UINT_UINT,
661 CAIRO_GOBJECT_TYPE_SURFACE, 2,
662 G_TYPE_UINT,
663 G_TYPE_UINT);
664
665 /**
666 * ClutterCairoTexture::draw:
667 * @texture: the #ClutterCairoTexture that emitted the signal
668 * @cr: the Cairo context to use to draw
669 *
670 * The ::draw signal is emitted each time a #ClutterCairoTexture has
671 * been invalidated.
672 *
673 * The passed Cairo context passed will be clipped to the invalidated
674 * area.
675 *
676 * It is safe to connect multiple callbacks to this signals; the state
677 * of the Cairo context passed to each callback is automatically saved
678 * and restored, so it's not necessary to call cairo_save() and
679 * cairo_restore().
680 *
681 * Return value: %TRUE if the signal emission should stop, and %FALSE
682 * to continue
683 *
684 * Since: 1.8
685 *
686 * Deprecated: 1.12
687 */
688 cairo_signals[DRAW] =
689 g_signal_new (I_("draw"),
690 G_TYPE_FROM_CLASS (klass),
691 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE,
692 G_STRUCT_OFFSET (ClutterCairoTextureClass, draw),
693 _clutter_boolean_handled_accumulator, NULL,
694 clutter_cairo_texture_draw_marshaller,
695 G_TYPE_BOOLEAN, 1,
696 CAIRO_GOBJECT_TYPE_CONTEXT);
697 }
698
699 static void
clutter_cairo_texture_init(ClutterCairoTexture * self)700 clutter_cairo_texture_init (ClutterCairoTexture *self)
701 {
702 self->priv = clutter_cairo_texture_get_instance_private (self);
703
704 /* FIXME - we are hardcoding the format; it would be good to have
705 * a :surface-format construct-only property for creating
706 * textures with a different format and have the cairo surface
707 * match that format
708 *
709 * priv->format = CAIRO_FORMAT_ARGB32;
710 */
711
712 /* the Cairo surface is responsible for driving the size of
713 * the texture; if we let sync_size to its default of TRUE,
714 * the Texture will try to queue a relayout every time we
715 * change the size of the Cairo surface - which is not what
716 * we want
717 */
718 clutter_texture_set_sync_size (CLUTTER_TEXTURE (self), FALSE);
719 }
720
721 /**
722 * clutter_cairo_texture_new:
723 * @width: the width of the surface
724 * @height: the height of the surface
725 *
726 * Creates a new #ClutterCairoTexture actor, with a surface of @width by
727 * @height pixels.
728 *
729 * Return value: the newly created #ClutterCairoTexture actor
730 *
731 * Since: 1.0
732 *
733 * Deprecated: 1.12: Use #ClutterCanvas instead
734 */
735 ClutterActor*
clutter_cairo_texture_new(guint width,guint height)736 clutter_cairo_texture_new (guint width,
737 guint height)
738 {
739 return g_object_new (CLUTTER_TYPE_CAIRO_TEXTURE,
740 "surface-width", width,
741 "surface-height", height,
742 NULL);
743 }
744
745 static void
intersect_rectangles(cairo_rectangle_int_t * a,cairo_rectangle_int_t * b,cairo_rectangle_int_t * inter)746 intersect_rectangles (cairo_rectangle_int_t *a,
747 cairo_rectangle_int_t *b,
748 cairo_rectangle_int_t *inter)
749 {
750 gint dest_x, dest_y;
751 gint dest_width, dest_height;
752
753 dest_x = MAX (a->x, b->x);
754 dest_y = MAX (a->y, b->y);
755 dest_width = MIN (a->x + a->width, b->x + b->width) - dest_x;
756 dest_height = MIN (a->y + a->height, b->y + b->height) - dest_y;
757
758 if (dest_width > 0 && dest_height > 0)
759 {
760 inter->x = dest_x;
761 inter->y = dest_y;
762 inter->width = dest_width;
763 inter->height = dest_height;
764 }
765 else
766 {
767 inter->x = 0;
768 inter->y = 0;
769 inter->width = 0;
770 inter->height = 0;
771 }
772 }
773
774 static cairo_t *
clutter_cairo_texture_create_region_internal(ClutterCairoTexture * self,gint x_offset,gint y_offset,gint width,gint height)775 clutter_cairo_texture_create_region_internal (ClutterCairoTexture *self,
776 gint x_offset,
777 gint y_offset,
778 gint width,
779 gint height)
780 {
781 ClutterCairoTexturePrivate *priv = self->priv;
782 cairo_rectangle_int_t region, area, inter;
783 cairo_surface_t *surface;
784 DrawContext *ctxt;
785 cairo_t *cr;
786
787 if (width < 0)
788 width = priv->surface_width;
789
790 if (height < 0)
791 height = priv->surface_height;
792
793 if (width == 0 || height == 0)
794 {
795 g_warning ("Unable to create a context for an image surface of "
796 "width %d and height %d. Set the surface size to be "
797 "at least 1 pixel by 1 pixel.",
798 width, height);
799 return NULL;
800 }
801
802 surface = get_surface (self);
803
804 ctxt = draw_context_create (self);
805
806 region.x = x_offset;
807 region.y = y_offset;
808 region.width = width;
809 region.height = height;
810
811 area.x = 0;
812 area.y = 0;
813 area.width = priv->surface_width;
814 area.height = priv->surface_height;
815
816 /* Limit the region to the visible rectangle */
817 intersect_rectangles (&area, ®ion, &inter);
818
819 ctxt->rect = inter;
820
821 cr = cairo_create (surface);
822 cairo_set_user_data (cr, &clutter_cairo_texture_context_key,
823 ctxt,
824 clutter_cairo_texture_context_destroy);
825
826 return cr;
827 }
828
829 /**
830 * clutter_cairo_texture_create_region:
831 * @self: a #ClutterCairoTexture
832 * @x_offset: offset of the region on the X axis
833 * @y_offset: offset of the region on the Y axis
834 * @width: width of the region, or -1 for the full surface width
835 * @height: height of the region, or -1 for the full surface height
836 *
837 * Creates a new Cairo context that will updat the region defined
838 * by @x_offset, @y_offset, @width and @height.
839 *
840 * Do not call this function within the paint virtual
841 * function or from a callback to the #ClutterActor::paint
842 * signal.
843 *
844 * Return value: a newly created Cairo context. Use cairo_destroy()
845 * to upload the contents of the context when done drawing
846 *
847 * Since: 1.0
848 *
849 * Deprecated: 1.8: Use the #ClutterCairoTexture::draw signal and
850 * clutter_cairo_texture_invalidate_rectangle() to obtain a
851 * clipped Cairo context for 2D drawing.
852 */
853 cairo_t *
clutter_cairo_texture_create_region(ClutterCairoTexture * self,gint x_offset,gint y_offset,gint width,gint height)854 clutter_cairo_texture_create_region (ClutterCairoTexture *self,
855 gint x_offset,
856 gint y_offset,
857 gint width,
858 gint height)
859 {
860 g_return_val_if_fail (CLUTTER_IS_CAIRO_TEXTURE (self), NULL);
861
862 clutter_warn_if_paint_fail (self);
863
864 return clutter_cairo_texture_create_region_internal (self,
865 x_offset, y_offset,
866 width, height);
867 }
868
869 /**
870 * clutter_cairo_texture_invalidate_rectangle:
871 * @self: a #ClutterCairoTexture
872 * @rect: (allow-none): a rectangle with the area to invalida,
873 * or %NULL to perform an unbounded invalidation
874 *
875 * Invalidates a rectangular region of a #ClutterCairoTexture.
876 *
877 * The invalidation will cause the #ClutterCairoTexture::draw signal
878 * to be emitted.
879 *
880 * See also: clutter_cairo_texture_invalidate()
881 *
882 * Since: 1.8
883 * Deprecated: 1.12: Use #ClutterCanvas instead
884 */
885 void
clutter_cairo_texture_invalidate_rectangle(ClutterCairoTexture * self,cairo_rectangle_int_t * rect)886 clutter_cairo_texture_invalidate_rectangle (ClutterCairoTexture *self,
887 cairo_rectangle_int_t *rect)
888 {
889 DrawContext *ctxt = NULL;
890
891 g_return_if_fail (CLUTTER_IS_CAIRO_TEXTURE (self));
892
893 if (self->priv->cr_context != NULL)
894 {
895 g_warning ("It is not possible to invalidate a Cairo texture"
896 "while drawing into it.");
897 return;
898 }
899
900 ctxt = draw_context_create (self);
901
902 if (rect != NULL)
903 {
904 cairo_rectangle_int_t area, inter;
905
906 area.x = 0;
907 area.y = 0;
908 area.width = self->priv->surface_width;
909 area.height = self->priv->surface_height;
910
911 /* Limit the region to the visible rectangle */
912 intersect_rectangles (&area, rect, &inter);
913
914 ctxt->is_clipped = TRUE;
915 ctxt->rect = inter;
916 }
917 else
918 {
919 ctxt->is_clipped = FALSE;
920 ctxt->rect.x = ctxt->rect.y = 0;
921 ctxt->rect.width = self->priv->surface_width;
922 ctxt->rect.height = self->priv->surface_height;
923 }
924
925 /* XXX - it might be good to move the emission inside the paint cycle
926 * using a repaint function, to avoid blocking inside this function
927 */
928 clutter_cairo_texture_emit_draw (self, ctxt);
929 }
930
931 /**
932 * clutter_cairo_texture_invalidate:
933 * @self: a #ClutterCairoTexture
934 *
935 * Invalidates the whole surface of a #ClutterCairoTexture.
936 *
937 * This function will cause the #ClutterCairoTexture::draw signal
938 * to be emitted.
939 *
940 * See also: clutter_cairo_texture_invalidate_rectangle()
941 *
942 * Since: 1.8
943 * Deprecated: 1.12: Use #ClutterCanvas instead
944 */
945 void
clutter_cairo_texture_invalidate(ClutterCairoTexture * self)946 clutter_cairo_texture_invalidate (ClutterCairoTexture *self)
947 {
948 g_return_if_fail (CLUTTER_IS_CAIRO_TEXTURE (self));
949
950 clutter_cairo_texture_invalidate_rectangle (self, NULL);
951 }
952
953 /**
954 * clutter_cairo_texture_create:
955 * @self: a #ClutterCairoTexture
956 *
957 * Creates a new Cairo context for the @cairo texture. It is
958 * similar to using clutter_cairo_texture_create_region() with @x_offset
959 * and @y_offset of 0, @width equal to the @cairo texture surface width
960 * and @height equal to the @cairo texture surface height.
961 *
962 * Do not call this function within the paint virtual
963 * function or from a callback to the #ClutterActor::paint
964 * signal.
965 *
966 * Return value: a newly created Cairo context. Use cairo_destroy()
967 * to upload the contents of the context when done drawing
968 *
969 * Since: 1.0
970 *
971 * Deprecated: 1.8: Use the #ClutterCairoTexture::draw signal and
972 * the clutter_cairo_texture_invalidate() function to obtain a
973 * Cairo context for 2D drawing.
974 */
975 cairo_t *
clutter_cairo_texture_create(ClutterCairoTexture * self)976 clutter_cairo_texture_create (ClutterCairoTexture *self)
977 {
978 g_return_val_if_fail (CLUTTER_IS_CAIRO_TEXTURE (self), NULL);
979
980 clutter_warn_if_paint_fail (self);
981
982 return clutter_cairo_texture_create_region_internal (self, 0, 0, -1, -1);
983 }
984
985 /**
986 * clutter_cairo_texture_set_surface_size:
987 * @self: a #ClutterCairoTexture
988 * @width: the new width of the surface
989 * @height: the new height of the surface
990 *
991 * Resizes the Cairo surface used by @self to @width and @height.
992 *
993 * This function will not invalidate the contents of the Cairo
994 * texture: you will have to explicitly call either
995 * clutter_cairo_texture_invalidate_rectangle() or
996 * clutter_cairo_texture_invalidate().
997 *
998 * Since: 1.0
999 * Deprecated: 1.12: Use #ClutterCanvas instead
1000 */
1001 void
clutter_cairo_texture_set_surface_size(ClutterCairoTexture * self,guint width,guint height)1002 clutter_cairo_texture_set_surface_size (ClutterCairoTexture *self,
1003 guint width,
1004 guint height)
1005 {
1006 ClutterCairoTexturePrivate *priv;
1007
1008 g_return_if_fail (CLUTTER_IS_CAIRO_TEXTURE (self));
1009
1010 priv = self->priv;
1011
1012 if (width == priv->surface_width &&
1013 height == priv->surface_height)
1014 return;
1015
1016 g_object_freeze_notify (G_OBJECT (self));
1017
1018 if (priv->surface_width != width)
1019 {
1020 priv->surface_width = width;
1021 g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_SURFACE_WIDTH]);
1022 }
1023
1024 if (priv->surface_height != height)
1025 {
1026 priv->surface_height = height;
1027 g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_SURFACE_HEIGHT]);
1028 }
1029
1030 clutter_cairo_texture_surface_resize_internal (self);
1031
1032 g_object_thaw_notify (G_OBJECT (self));
1033 }
1034
1035 /**
1036 * clutter_cairo_texture_get_surface_size:
1037 * @self: a #ClutterCairoTexture
1038 * @width: (out): return location for the surface width, or %NULL
1039 * @height: (out): return location for the surface height, or %NULL
1040 *
1041 * Retrieves the surface width and height for @self.
1042 *
1043 * Since: 1.0
1044 * Deprecated: 1.12: Use #ClutterCanvas instead
1045 */
1046 void
clutter_cairo_texture_get_surface_size(ClutterCairoTexture * self,guint * width,guint * height)1047 clutter_cairo_texture_get_surface_size (ClutterCairoTexture *self,
1048 guint *width,
1049 guint *height)
1050 {
1051 g_return_if_fail (CLUTTER_IS_CAIRO_TEXTURE (self));
1052
1053 if (width)
1054 *width = self->priv->surface_width;
1055
1056 if (height)
1057 *height = self->priv->surface_height;
1058 }
1059
1060 /**
1061 * clutter_cairo_texture_clear:
1062 * @self: a #ClutterCairoTexture
1063 *
1064 * Clears @self's internal drawing surface, so that the next upload
1065 * will replace the previous contents of the #ClutterCairoTexture
1066 * rather than adding to it.
1067 *
1068 * Calling this function from within a #ClutterCairoTexture::draw
1069 * signal handler will clear the invalidated area.
1070 *
1071 * Since: 1.0
1072 * Deprecated: 1.12: Use #ClutterCanvas instead
1073 */
1074 void
clutter_cairo_texture_clear(ClutterCairoTexture * self)1075 clutter_cairo_texture_clear (ClutterCairoTexture *self)
1076 {
1077 ClutterCairoTexturePrivate *priv;
1078 cairo_t *cr;
1079
1080 g_return_if_fail (CLUTTER_IS_CAIRO_TEXTURE (self));
1081
1082 priv = self->priv;
1083
1084 /* if we got called outside of a ::draw signal handler
1085 * then we clear the whole surface by creating a temporary
1086 * cairo_t; otherwise, we clear the current cairo_t, which
1087 * will take into account the clip region.
1088 */
1089 if (priv->cr_context == NULL)
1090 {
1091 cairo_surface_t *surface;
1092
1093 surface = get_surface (self);
1094
1095 cr = cairo_create (surface);
1096 }
1097 else
1098 cr = priv->cr_context;
1099
1100 cairo_save (cr);
1101
1102 cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
1103 cairo_paint (cr);
1104
1105 cairo_restore (cr);
1106
1107 if (priv->cr_context == NULL)
1108 cairo_destroy (cr);
1109 }
1110
1111 /**
1112 * clutter_cairo_texture_set_auto_resize:
1113 * @self: a #ClutterCairoTexture
1114 * @value: %TRUE if the #ClutterCairoTexture should bind the surface
1115 * size to the allocation
1116 *
1117 * Sets whether the #ClutterCairoTexture should ensure that the
1118 * backing Cairo surface used matches the allocation assigned to
1119 * the actor. If the allocation changes, the contents of the
1120 * #ClutterCairoTexture will also be invalidated automatically.
1121 *
1122 * Since: 1.8
1123 * Deprecated: 1.12: Use #ClutterCanvas instead
1124 */
1125 void
clutter_cairo_texture_set_auto_resize(ClutterCairoTexture * self,gboolean value)1126 clutter_cairo_texture_set_auto_resize (ClutterCairoTexture *self,
1127 gboolean value)
1128 {
1129 ClutterCairoTexturePrivate *priv;
1130
1131 g_return_if_fail (CLUTTER_IS_CAIRO_TEXTURE (self));
1132
1133 value = !!value;
1134
1135 priv = self->priv;
1136
1137 if (priv->auto_resize == value)
1138 return;
1139
1140 priv->auto_resize = value;
1141
1142 clutter_actor_queue_relayout (CLUTTER_ACTOR (self));
1143
1144 g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_AUTO_RESIZE]);
1145 }
1146
1147 /**
1148 * clutter_cairo_texture_get_auto_resize:
1149 * @self: a #ClutterCairoTexture
1150 *
1151 * Retrieves the value set using clutter_cairo_texture_set_auto_resize().
1152 *
1153 * Return value: %TRUE if the #ClutterCairoTexture should track the
1154 * allocation, and %FALSE otherwise
1155 *
1156 * Since: 1.8
1157 * Deprecated: 1.12: Use #ClutterCanvas instead
1158 */
1159 gboolean
clutter_cairo_texture_get_auto_resize(ClutterCairoTexture * self)1160 clutter_cairo_texture_get_auto_resize (ClutterCairoTexture *self)
1161 {
1162 g_return_val_if_fail (CLUTTER_IS_CAIRO_TEXTURE (self), FALSE);
1163
1164 return self->priv->auto_resize;
1165 }
1166