mirror of
https://github.com/brl/mutter.git
synced 2024-11-27 02:20:43 -05:00
7f8838d7cc
Keeping the backing Cairo surface of a CairoTexture canvas in sync with the actor's allocation is tedious and prone to mistakes. We can definitely do better by simply exposing a property that does the surface resize and invalidation automagically on ::allocate.
1173 lines
34 KiB
C
1173 lines
34 KiB
C
/*
|
|
* Clutter
|
|
*
|
|
* An OpenGL based 'interactive canvas' library.
|
|
*
|
|
* Authored By: Emmanuele Bassi <ebassi@linux.intel.com>
|
|
* Matthew Allum <mallum@o-hand.com>
|
|
* Chris Lord <chris@o-hand.com>
|
|
* Iain Holmes <iain@o-hand.com>
|
|
* Neil Roberts <neil@linux.intel.com>
|
|
*
|
|
* Copyright (C) 2008, 2009, 2010, 2011 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/**
|
|
* SECTION:clutter-cairo-texture
|
|
* @short_description: Texture with Cairo integration
|
|
*
|
|
* #ClutterCairoTexture is a #ClutterTexture that displays the contents
|
|
* of a Cairo context. The #ClutterCairoTexture actor will create a
|
|
* Cairo image surface which will then be uploaded to a GL texture when
|
|
* needed.
|
|
*
|
|
* <note><para>Since #ClutterCairoTexture uses a Cairo image surface
|
|
* internally all the drawing operations will be performed in
|
|
* software and not using hardware acceleration. This can lead to
|
|
* performance degradation if the contents of the texture change
|
|
* frequently.</para></note>
|
|
*
|
|
* In order to use a #ClutterCairoTexture you should connect to the
|
|
* #ClutterCairoTexture::draw signal; the signal is emitted each time
|
|
* the #ClutterCairoTexture has been told to invalidate its contents,
|
|
* by using clutter_cairo_texture_invalidate_rectangle() or its
|
|
* sister function, clutter_cairo_texture_invalidate().
|
|
*
|
|
* Each callback to the #ClutterCairoTexture::draw signal will receive
|
|
* a #cairo_t context which can be used for drawing; the Cairo context
|
|
* is owned by the #ClutterCairoTexture and should not be destroyed
|
|
* explicitly.
|
|
*
|
|
* <example id="cairo-texture-example">
|
|
* <title>A simple ClutterCairoTexture canvas</title>
|
|
* <programlisting>
|
|
* <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" parse="text" href="../../../../tests/interactive/test-cairo-clock.c">
|
|
* <xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback>
|
|
* </xi:include>
|
|
* </programlisting>
|
|
* </example>
|
|
*
|
|
* #ClutterCairoTexture is available since Clutter 1.0.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <math.h>
|
|
|
|
#include <cairo-gobject.h>
|
|
|
|
#include "clutter-cairo-texture.h"
|
|
|
|
#include "clutter-actor-private.h"
|
|
#include "clutter-debug.h"
|
|
#include "clutter-marshal.h"
|
|
#include "clutter-private.h"
|
|
|
|
G_DEFINE_TYPE (ClutterCairoTexture,
|
|
clutter_cairo_texture,
|
|
CLUTTER_TYPE_TEXTURE);
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
|
|
PROP_SURFACE_WIDTH,
|
|
PROP_SURFACE_HEIGHT,
|
|
PROP_AUTO_RESIZE,
|
|
|
|
PROP_LAST
|
|
};
|
|
|
|
enum
|
|
{
|
|
CREATE_SURFACE,
|
|
DRAW,
|
|
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
static GParamSpec *obj_props[PROP_LAST] = { NULL, };
|
|
|
|
static guint cairo_signals[LAST_SIGNAL] = { 0, };
|
|
|
|
#ifdef CLUTTER_ENABLE_DEBUG
|
|
#define clutter_warn_if_paint_fail(obj) G_STMT_START { \
|
|
if (CLUTTER_ACTOR_IN_PAINT (obj)) { \
|
|
g_warning ("%s should not be called during the paint sequence " \
|
|
"of a ClutterCairoTexture as it will likely cause " \
|
|
"performance issues.", G_STRFUNC); \
|
|
} } G_STMT_END
|
|
#else
|
|
#define clutter_warn_if_paint_fail(obj) /* void */
|
|
#endif /* CLUTTER_ENABLE_DEBUG */
|
|
|
|
#define CLUTTER_CAIRO_TEXTURE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLUTTER_TYPE_CAIRO_TEXTURE, ClutterCairoTexturePrivate))
|
|
|
|
struct _ClutterCairoTexturePrivate
|
|
{
|
|
cairo_surface_t *cr_surface;
|
|
|
|
guint surface_width;
|
|
guint surface_height;
|
|
|
|
cairo_t *cr_context;
|
|
|
|
guint auto_resize : 1;
|
|
};
|
|
|
|
typedef struct {
|
|
ClutterCairoTexture *texture;
|
|
|
|
cairo_rectangle_int_t rect;
|
|
|
|
guint is_clipped : 1;
|
|
} DrawContext;
|
|
|
|
static const cairo_user_data_key_t clutter_cairo_texture_context_key;
|
|
|
|
static DrawContext *
|
|
draw_context_create (ClutterCairoTexture *texture)
|
|
{
|
|
DrawContext *context = g_slice_new0 (DrawContext);
|
|
|
|
context->texture = g_object_ref (texture);
|
|
|
|
return context;
|
|
}
|
|
|
|
static void
|
|
draw_context_destroy (gpointer data)
|
|
{
|
|
if (G_LIKELY (data != NULL))
|
|
{
|
|
DrawContext *context = data;
|
|
|
|
g_object_unref (context->texture);
|
|
|
|
g_slice_free (DrawContext, data);
|
|
}
|
|
}
|
|
|
|
static void
|
|
clutter_cairo_texture_set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
ClutterCairoTexturePrivate *priv;
|
|
|
|
priv = CLUTTER_CAIRO_TEXTURE (object)->priv;
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_SURFACE_WIDTH:
|
|
/* we perform the resize on notify to coalesce separate
|
|
* surface-width/surface-height property set
|
|
*/
|
|
priv->surface_width = g_value_get_uint (value);
|
|
break;
|
|
|
|
case PROP_SURFACE_HEIGHT:
|
|
priv->surface_height = g_value_get_uint (value);
|
|
break;
|
|
|
|
case PROP_AUTO_RESIZE:
|
|
clutter_cairo_texture_set_auto_resize (CLUTTER_CAIRO_TEXTURE (object),
|
|
g_value_get_boolean (value));
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
clutter_cairo_texture_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
ClutterCairoTexturePrivate *priv;
|
|
|
|
priv = CLUTTER_CAIRO_TEXTURE (object)->priv;
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_SURFACE_WIDTH:
|
|
g_value_set_uint (value, priv->surface_width);
|
|
break;
|
|
|
|
case PROP_SURFACE_HEIGHT:
|
|
g_value_set_uint (value, priv->surface_height);
|
|
break;
|
|
|
|
case PROP_AUTO_RESIZE:
|
|
g_value_set_boolean (value, priv->auto_resize);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
clutter_cairo_texture_finalize (GObject *object)
|
|
{
|
|
ClutterCairoTexturePrivate *priv = CLUTTER_CAIRO_TEXTURE (object)->priv;
|
|
|
|
if (priv->cr_surface != NULL)
|
|
{
|
|
cairo_surface_t *surface = priv->cr_surface;
|
|
|
|
priv->cr_surface = NULL;
|
|
|
|
cairo_surface_finish (surface);
|
|
cairo_surface_destroy (surface);
|
|
}
|
|
|
|
G_OBJECT_CLASS (clutter_cairo_texture_parent_class)->finalize (object);
|
|
}
|
|
|
|
static cairo_surface_t *
|
|
get_surface (ClutterCairoTexture *self)
|
|
{
|
|
ClutterCairoTexturePrivate *priv = self->priv;
|
|
|
|
if (priv->cr_surface == NULL)
|
|
{
|
|
g_signal_emit (self, cairo_signals[CREATE_SURFACE], 0,
|
|
priv->surface_width,
|
|
priv->surface_height,
|
|
&priv->cr_surface);
|
|
}
|
|
|
|
return priv->cr_surface;
|
|
}
|
|
|
|
static void
|
|
clutter_cairo_texture_context_destroy (void *data)
|
|
{
|
|
DrawContext *ctxt = data;
|
|
ClutterCairoTexture *cairo = ctxt->texture;
|
|
ClutterCairoTexturePrivate *priv = cairo->priv;
|
|
guint8 *cairo_data;
|
|
gint cairo_width, cairo_height, cairo_stride;
|
|
gint surface_width, surface_height;
|
|
CoglHandle cogl_texture;
|
|
|
|
if (priv->cr_surface == NULL)
|
|
{
|
|
/* the surface went away before we could use it */
|
|
draw_context_destroy (ctxt);
|
|
return;
|
|
}
|
|
|
|
/* for any other surface type, we presume that there exists a native
|
|
* communication between Cairo and GL that is triggered by cairo_destroy().
|
|
*
|
|
* for instance, cairo-drm will flush the outstanding modifications to the
|
|
* surface upon context destruction and so the texture is automatically
|
|
* updated.
|
|
*/
|
|
if (cairo_surface_get_type (priv->cr_surface) != CAIRO_SURFACE_TYPE_IMAGE)
|
|
goto out;
|
|
|
|
surface_width = cairo_image_surface_get_width (priv->cr_surface);
|
|
surface_height = cairo_image_surface_get_height (priv->cr_surface);
|
|
|
|
cairo_width = MIN (ctxt->rect.width, surface_width);
|
|
cairo_height = MIN (ctxt->rect.height, surface_height);
|
|
|
|
cogl_texture = clutter_texture_get_cogl_texture (CLUTTER_TEXTURE (cairo));
|
|
if (cairo_width == 0 ||
|
|
cairo_height == 0 ||
|
|
cogl_texture == COGL_INVALID_HANDLE)
|
|
{
|
|
draw_context_destroy (ctxt);
|
|
return;
|
|
}
|
|
|
|
cairo_stride = cairo_image_surface_get_stride (priv->cr_surface);
|
|
cairo_data = cairo_image_surface_get_data (priv->cr_surface);
|
|
cairo_data += cairo_stride * ctxt->rect.y;
|
|
cairo_data += 4 * ctxt->rect.x;
|
|
|
|
cogl_texture_set_region (cogl_texture,
|
|
0, 0,
|
|
ctxt->rect.x, ctxt->rect.y,
|
|
cairo_width, cairo_height,
|
|
cairo_width, cairo_height,
|
|
CLUTTER_CAIRO_FORMAT_ARGB32,
|
|
cairo_stride,
|
|
cairo_data);
|
|
|
|
out:
|
|
draw_context_destroy (ctxt);
|
|
clutter_actor_queue_redraw (CLUTTER_ACTOR (cairo));
|
|
}
|
|
|
|
static inline void
|
|
clutter_cairo_texture_emit_draw (ClutterCairoTexture *self,
|
|
DrawContext *ctxt)
|
|
{
|
|
gboolean result;
|
|
cairo_t *cr;
|
|
|
|
g_assert (self->priv->cr_surface != NULL);
|
|
|
|
cr = cairo_create (self->priv->cr_surface);
|
|
cairo_set_user_data (cr, &clutter_cairo_texture_context_key,
|
|
ctxt,
|
|
clutter_cairo_texture_context_destroy);
|
|
|
|
if (ctxt->is_clipped)
|
|
{
|
|
cairo_rectangle (cr,
|
|
ctxt->rect.x,
|
|
ctxt->rect.y,
|
|
ctxt->rect.width,
|
|
ctxt->rect.height);
|
|
cairo_clip (cr);
|
|
}
|
|
|
|
/* store the cairo_t as a guard */
|
|
self->priv->cr_context = cr;
|
|
|
|
g_signal_emit (self, cairo_signals[DRAW], 0, cr, &result);
|
|
|
|
self->priv->cr_context = NULL;
|
|
|
|
cairo_destroy (cr);
|
|
}
|
|
|
|
static inline void
|
|
clutter_cairo_texture_surface_resize_internal (ClutterCairoTexture *cairo)
|
|
{
|
|
ClutterCairoTexturePrivate *priv = cairo->priv;
|
|
|
|
if (priv->cr_surface != NULL)
|
|
{
|
|
cairo_surface_t *surface = priv->cr_surface;
|
|
|
|
/* if the surface is an image one, and the size is already the
|
|
* same, then we don't need to do anything
|
|
*/
|
|
if (cairo_surface_get_type (surface) != CAIRO_SURFACE_TYPE_IMAGE)
|
|
{
|
|
gint surface_width = cairo_image_surface_get_width (surface);
|
|
gint surface_height = cairo_image_surface_get_height (surface);
|
|
|
|
if (priv->surface_width == surface_width &&
|
|
priv->surface_height == surface_height)
|
|
return;
|
|
}
|
|
|
|
cairo_surface_finish (surface);
|
|
cairo_surface_destroy (surface);
|
|
priv->cr_surface = NULL;
|
|
}
|
|
|
|
if (priv->surface_width == 0 ||
|
|
priv->surface_height == 0)
|
|
return;
|
|
|
|
g_signal_emit (cairo, cairo_signals[CREATE_SURFACE], 0,
|
|
priv->surface_width,
|
|
priv->surface_height,
|
|
&priv->cr_surface);
|
|
}
|
|
|
|
static void
|
|
clutter_cairo_texture_notify (GObject *object,
|
|
GParamSpec *pspec)
|
|
{
|
|
/* When the surface width or height changes then resize the cairo
|
|
surface. This is done here instead of directly in set_property so
|
|
that if both the width and height properties are set using a
|
|
single call to g_object_set then the surface will only be resized
|
|
once because the notifications will be frozen in between */
|
|
|
|
if (obj_props[PROP_SURFACE_WIDTH]->name == pspec->name ||
|
|
obj_props[PROP_SURFACE_HEIGHT]->name == pspec->name)
|
|
{
|
|
ClutterCairoTexture *cairo = CLUTTER_CAIRO_TEXTURE (object);
|
|
|
|
clutter_cairo_texture_surface_resize_internal (cairo);
|
|
}
|
|
|
|
if (G_OBJECT_CLASS (clutter_cairo_texture_parent_class)->notify)
|
|
G_OBJECT_CLASS (clutter_cairo_texture_parent_class)->notify (object, pspec);
|
|
}
|
|
|
|
static void
|
|
clutter_cairo_texture_get_preferred_width (ClutterActor *actor,
|
|
gfloat for_height,
|
|
gfloat *min_width,
|
|
gfloat *natural_width)
|
|
{
|
|
ClutterCairoTexturePrivate *priv = CLUTTER_CAIRO_TEXTURE (actor)->priv;
|
|
|
|
if (min_width)
|
|
*min_width = 0;
|
|
|
|
if (natural_width)
|
|
*natural_width = (gfloat) priv->surface_width;
|
|
}
|
|
|
|
static void
|
|
clutter_cairo_texture_get_preferred_height (ClutterActor *actor,
|
|
gfloat for_width,
|
|
gfloat *min_height,
|
|
gfloat *natural_height)
|
|
{
|
|
ClutterCairoTexturePrivate *priv = CLUTTER_CAIRO_TEXTURE (actor)->priv;
|
|
|
|
if (min_height)
|
|
*min_height = 0;
|
|
|
|
if (natural_height)
|
|
*natural_height = (gfloat) priv->surface_height;
|
|
}
|
|
|
|
static void
|
|
clutter_cairo_texture_allocate (ClutterActor *self,
|
|
const ClutterActorBox *allocation,
|
|
ClutterAllocationFlags flags)
|
|
{
|
|
ClutterCairoTexturePrivate *priv = CLUTTER_CAIRO_TEXTURE (self)->priv;
|
|
ClutterActorClass *parent_class;
|
|
|
|
parent_class = CLUTTER_ACTOR_CLASS (clutter_cairo_texture_parent_class);
|
|
parent_class->allocate (self, allocation, flags);
|
|
|
|
if (priv->auto_resize)
|
|
{
|
|
ClutterCairoTexture *texture = CLUTTER_CAIRO_TEXTURE (self);
|
|
gfloat width, height;
|
|
|
|
clutter_actor_box_get_size (allocation, &width, &height);
|
|
|
|
priv->surface_width = ceilf (width + 0.5);
|
|
priv->surface_height = ceilf (height + 0.5);
|
|
|
|
clutter_cairo_texture_surface_resize_internal (texture);
|
|
clutter_cairo_texture_invalidate (texture);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
clutter_cairo_texture_get_paint_volume (ClutterActor *self,
|
|
ClutterPaintVolume *volume)
|
|
{
|
|
return _clutter_actor_set_default_paint_volume (self,
|
|
CLUTTER_TYPE_CAIRO_TEXTURE,
|
|
volume);
|
|
}
|
|
|
|
static cairo_surface_t *
|
|
clutter_cairo_texture_create_surface (ClutterCairoTexture *self,
|
|
guint width,
|
|
guint height)
|
|
{
|
|
cairo_surface_t *surface;
|
|
guint cairo_stride;
|
|
guint8 *cairo_data;
|
|
CoglHandle cogl_texture;
|
|
|
|
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
|
|
width,
|
|
height);
|
|
|
|
cairo_stride = cairo_image_surface_get_stride (surface);
|
|
cairo_data = cairo_image_surface_get_data (surface);
|
|
|
|
/* create a backing Cogl texture */
|
|
cogl_texture = cogl_texture_new_from_data (width, height,
|
|
COGL_TEXTURE_NONE,
|
|
CLUTTER_CAIRO_FORMAT_ARGB32,
|
|
COGL_PIXEL_FORMAT_ANY,
|
|
cairo_stride,
|
|
cairo_data);
|
|
clutter_texture_set_cogl_texture (CLUTTER_TEXTURE (self), cogl_texture);
|
|
cogl_handle_unref (cogl_texture);
|
|
|
|
return surface;
|
|
}
|
|
|
|
static gboolean
|
|
create_surface_accum (GSignalInvocationHint *ihint,
|
|
GValue *return_accu,
|
|
const GValue *handler_return,
|
|
gpointer data)
|
|
{
|
|
g_value_copy (handler_return, return_accu);
|
|
|
|
/* stop on the first non-NULL return value */
|
|
return g_value_get_boxed (handler_return) == NULL;
|
|
}
|
|
|
|
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)
|
|
{
|
|
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_cairo_texture_class_init (ClutterCairoTextureClass *klass)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
|
|
|
|
gobject_class->finalize = clutter_cairo_texture_finalize;
|
|
gobject_class->set_property = clutter_cairo_texture_set_property;
|
|
gobject_class->get_property = clutter_cairo_texture_get_property;
|
|
gobject_class->notify = clutter_cairo_texture_notify;
|
|
|
|
actor_class->get_paint_volume =
|
|
clutter_cairo_texture_get_paint_volume;
|
|
actor_class->get_preferred_width =
|
|
clutter_cairo_texture_get_preferred_width;
|
|
actor_class->get_preferred_height =
|
|
clutter_cairo_texture_get_preferred_height;
|
|
actor_class->allocate =
|
|
clutter_cairo_texture_allocate;
|
|
|
|
klass->create_surface = clutter_cairo_texture_create_surface;
|
|
|
|
g_type_class_add_private (gobject_class, sizeof (ClutterCairoTexturePrivate));
|
|
|
|
/**
|
|
* ClutterCairoTexture:surface-width:
|
|
*
|
|
* The width of the Cairo surface used by the #ClutterCairoTexture
|
|
* actor, in pixels.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
obj_props[PROP_SURFACE_WIDTH] =
|
|
g_param_spec_uint ("surface-width",
|
|
P_("Surface Width"),
|
|
P_("The width of the Cairo surface"),
|
|
0, G_MAXUINT,
|
|
0,
|
|
CLUTTER_PARAM_READWRITE);
|
|
/**
|
|
* ClutterCairoTexture:surface-height:
|
|
*
|
|
* The height of the Cairo surface used by the #ClutterCairoTexture
|
|
* actor, in pixels.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
obj_props[PROP_SURFACE_HEIGHT] =
|
|
g_param_spec_uint ("surface-height",
|
|
P_("Surface Height"),
|
|
P_("The height of the Cairo surface"),
|
|
0, G_MAXUINT,
|
|
0,
|
|
CLUTTER_PARAM_READWRITE);
|
|
|
|
/**
|
|
* ClutterCairoTexture:auto-resize:
|
|
*
|
|
* Controls whether the #ClutterCairoTexture should automatically
|
|
* resize the Cairo surface whenever the actor's allocation changes.
|
|
* If :auto-resize is set to %TRUE the surface contents will also
|
|
* be invalidated automatically.
|
|
*
|
|
* Since: 1.8
|
|
*/
|
|
obj_props[PROP_AUTO_RESIZE] =
|
|
g_param_spec_boolean ("auto-resize",
|
|
P_("Auto Resize"),
|
|
P_("Whether the surface should match the allocation"),
|
|
FALSE,
|
|
CLUTTER_PARAM_READWRITE);
|
|
|
|
g_object_class_install_properties (gobject_class, PROP_LAST, obj_props);
|
|
|
|
/**
|
|
* ClutterCairoTexture::create-surface:
|
|
* @texture: the #ClutterCairoTexture that emitted the signal
|
|
* @width: the width of the surface to create
|
|
* @height: the height of the surface to create
|
|
*
|
|
* The ::create-surface signal is emitted when a #ClutterCairoTexture
|
|
* news its surface (re)created, which happens either when the Cairo
|
|
* context is created with clutter_cairo_texture_create() or
|
|
* clutter_cairo_texture_create_region(), or when the surface is resized
|
|
* through clutter_cairo_texture_set_surface_size().
|
|
*
|
|
* The first signal handler that returns a non-%NULL, valid surface will
|
|
* stop any further signal emission, and the returned surface will be
|
|
* the one used.
|
|
*
|
|
* Return value: the newly created #cairo_surface_t for the texture
|
|
*
|
|
* Since: 1.6
|
|
*/
|
|
cairo_signals[CREATE_SURFACE] =
|
|
g_signal_new (I_("create-surface"),
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE,
|
|
G_STRUCT_OFFSET (ClutterCairoTextureClass, create_surface),
|
|
create_surface_accum, NULL,
|
|
_clutter_marshal_BOXED__UINT_UINT,
|
|
CAIRO_GOBJECT_TYPE_SURFACE, 2,
|
|
G_TYPE_UINT,
|
|
G_TYPE_UINT);
|
|
|
|
/**
|
|
* ClutterCairoTexture::draw:
|
|
* @texture: the #ClutterCairoTexture that emitted the signal
|
|
* @cr: the Cairo context to use to draw
|
|
*
|
|
* The ::draw signal is emitted each time a #ClutterCairoTexture has
|
|
* been invalidated.
|
|
*
|
|
* The passed Cairo context passed will be clipped to the invalidated
|
|
* area.
|
|
*
|
|
* It is safe to connect multiple callbacks to this signals; the state
|
|
* of the Cairo context passed to each callback is automatically saved
|
|
* and restored, so it's not necessary to call cairo_save() and
|
|
* cairo_restore().
|
|
*
|
|
* Return value: %TRUE if the signal emission should stop, and %FALSE
|
|
* to continue
|
|
*
|
|
* Since: 1.8
|
|
*/
|
|
cairo_signals[DRAW] =
|
|
g_signal_new (I_("draw"),
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE,
|
|
G_STRUCT_OFFSET (ClutterCairoTextureClass, draw),
|
|
_clutter_boolean_handled_accumulator, NULL,
|
|
clutter_cairo_texture_draw_marshaller,
|
|
G_TYPE_BOOLEAN, 1,
|
|
CAIRO_GOBJECT_TYPE_CONTEXT);
|
|
}
|
|
|
|
static void
|
|
clutter_cairo_texture_init (ClutterCairoTexture *self)
|
|
{
|
|
ClutterCairoTexturePrivate *priv;
|
|
|
|
self->priv = priv = CLUTTER_CAIRO_TEXTURE_GET_PRIVATE (self);
|
|
|
|
/* FIXME - we are hardcoding the format; it would be good to have
|
|
* a :surface-format construct-only property for creating
|
|
* textures with a different format and have the cairo surface
|
|
* match that format
|
|
*
|
|
* priv->format = CAIRO_FORMAT_ARGB32;
|
|
*/
|
|
|
|
/* the Cairo surface is responsible for driving the size of
|
|
* the texture; if we let sync_size to its default of TRUE,
|
|
* the Texture will try to queue a relayout every time we
|
|
* change the size of the Cairo surface - which is not what
|
|
* we want
|
|
*/
|
|
clutter_texture_set_sync_size (CLUTTER_TEXTURE (self), FALSE);
|
|
}
|
|
|
|
/**
|
|
* clutter_cairo_texture_new:
|
|
* @width: the width of the surface
|
|
* @height: the height of the surface
|
|
*
|
|
* Creates a new #ClutterCairoTexture actor, with a surface of @width by
|
|
* @height pixels.
|
|
*
|
|
* Return value: the newly created #ClutterCairoTexture actor
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
ClutterActor*
|
|
clutter_cairo_texture_new (guint width,
|
|
guint height)
|
|
{
|
|
return g_object_new (CLUTTER_TYPE_CAIRO_TEXTURE,
|
|
"surface-width", width,
|
|
"surface-height", height,
|
|
NULL);
|
|
}
|
|
|
|
static void
|
|
intersect_rectangles (cairo_rectangle_int_t *a,
|
|
cairo_rectangle_int_t *b,
|
|
cairo_rectangle_int_t *inter)
|
|
{
|
|
gint dest_x, dest_y;
|
|
gint dest_width, dest_height;
|
|
|
|
dest_x = MAX (a->x, b->x);
|
|
dest_y = MAX (a->y, b->y);
|
|
dest_width = MIN (a->x + a->width, b->x + b->width) - dest_x;
|
|
dest_height = MIN (a->y + a->height, b->y + b->height) - dest_y;
|
|
|
|
if (dest_width > 0 && dest_height > 0)
|
|
{
|
|
inter->x = dest_x;
|
|
inter->y = dest_y;
|
|
inter->width = dest_width;
|
|
inter->height = dest_height;
|
|
}
|
|
else
|
|
{
|
|
inter->x = 0;
|
|
inter->y = 0;
|
|
inter->width = 0;
|
|
inter->height = 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* clutter_cairo_texture_create_region:
|
|
* @self: a #ClutterCairoTexture
|
|
* @x_offset: offset of the region on the X axis
|
|
* @y_offset: offset of the region on the Y axis
|
|
* @width: width of the region, or -1 for the full surface width
|
|
* @height: height of the region, or -1 for the full surface height
|
|
*
|
|
* Creates a new Cairo context that will updat the region defined
|
|
* by @x_offset, @y_offset, @width and @height.
|
|
*
|
|
* <warning><para>Do not call this function within the paint virtual
|
|
* function or from a callback to the #ClutterActor::paint
|
|
* signal.</para></warning>
|
|
*
|
|
* Return value: a newly created Cairo context. Use cairo_destroy()
|
|
* to upload the contents of the context when done drawing
|
|
*
|
|
* Since: 1.0
|
|
*
|
|
* Deprecated: 1.8: Use the #ClutterCairoTexture::draw signal and
|
|
* clutter_cairo_texture_invalidate_rectangle() to obtain a
|
|
* clipped Cairo context for 2D drawing.
|
|
*/
|
|
cairo_t *
|
|
clutter_cairo_texture_create_region (ClutterCairoTexture *self,
|
|
gint x_offset,
|
|
gint y_offset,
|
|
gint width,
|
|
gint height)
|
|
{
|
|
ClutterCairoTexturePrivate *priv;
|
|
DrawContext *ctxt;
|
|
cairo_rectangle_int_t region, area, inter;
|
|
cairo_surface_t *surface;
|
|
cairo_t *cr;
|
|
|
|
g_return_val_if_fail (CLUTTER_IS_CAIRO_TEXTURE (self), NULL);
|
|
|
|
clutter_warn_if_paint_fail (self);
|
|
|
|
priv = self->priv;
|
|
|
|
if (width < 0)
|
|
width = priv->surface_width;
|
|
|
|
if (height < 0)
|
|
height = priv->surface_height;
|
|
|
|
if (width == 0 || height == 0)
|
|
{
|
|
g_warning ("Unable to create a context for an image surface of "
|
|
"width %d and height %d. Set the surface size to be "
|
|
"at least 1 pixel by 1 pixel.",
|
|
width, height);
|
|
return NULL;
|
|
}
|
|
|
|
surface = get_surface (self);
|
|
|
|
ctxt = draw_context_create (self);
|
|
|
|
region.x = x_offset;
|
|
region.y = y_offset;
|
|
region.width = width;
|
|
region.height = height;
|
|
|
|
area.x = 0;
|
|
area.y = 0;
|
|
area.width = priv->surface_width;
|
|
area.height = priv->surface_height;
|
|
|
|
/* Limit the region to the visible rectangle */
|
|
intersect_rectangles (&area, ®ion, &inter);
|
|
|
|
ctxt->rect = inter;
|
|
|
|
cr = cairo_create (surface);
|
|
cairo_set_user_data (cr, &clutter_cairo_texture_context_key,
|
|
ctxt,
|
|
clutter_cairo_texture_context_destroy);
|
|
|
|
return cr;
|
|
}
|
|
|
|
/**
|
|
* clutter_cairo_texture_invalidate_rectangle:
|
|
* @self: a #ClutterCairoTexture
|
|
* @rect: (allow-none): a rectangle with the area to invalida,
|
|
* or %NULL to perform an unbounded invalidation
|
|
*
|
|
* Invalidates a rectangular region of a #ClutterCairoTexture.
|
|
*
|
|
* The invalidation will cause the #ClutterCairoTexture::draw signal
|
|
* to be emitted.
|
|
*
|
|
* See also: clutter_cairo_texture_invalidate()
|
|
*
|
|
* Since: 1.8
|
|
*/
|
|
void
|
|
clutter_cairo_texture_invalidate_rectangle (ClutterCairoTexture *self,
|
|
cairo_rectangle_int_t *rect)
|
|
{
|
|
DrawContext *ctxt = NULL;
|
|
|
|
g_return_if_fail (CLUTTER_IS_CAIRO_TEXTURE (self));
|
|
|
|
if (self->priv->cr_context != NULL)
|
|
{
|
|
g_warning ("It is not possible to invalidate a Cairo texture"
|
|
"while drawing into it.");
|
|
return;
|
|
}
|
|
|
|
ctxt = draw_context_create (self);
|
|
|
|
if (rect != NULL)
|
|
{
|
|
cairo_rectangle_int_t area, inter;
|
|
|
|
area.x = 0;
|
|
area.y = 0;
|
|
area.width = self->priv->surface_width;
|
|
area.height = self->priv->surface_height;
|
|
|
|
/* Limit the region to the visible rectangle */
|
|
intersect_rectangles (&area, rect, &inter);
|
|
|
|
ctxt->is_clipped = TRUE;
|
|
ctxt->rect = inter;
|
|
}
|
|
else
|
|
{
|
|
ctxt->is_clipped = FALSE;
|
|
ctxt->rect.x = ctxt->rect.y = 0;
|
|
ctxt->rect.width = self->priv->surface_width;
|
|
ctxt->rect.height = self->priv->surface_height;
|
|
}
|
|
|
|
/* XXX - it might be good to move the emission inside the paint cycle
|
|
* using a repaint function, to avoid blocking inside this function
|
|
*/
|
|
clutter_cairo_texture_emit_draw (self, ctxt);
|
|
}
|
|
|
|
/**
|
|
* clutter_cairo_texture_invalidate:
|
|
* @self: a #ClutterCairoTexture
|
|
*
|
|
* Invalidates the whole surface of a #ClutterCairoTexture.
|
|
*
|
|
* This function will cause the #ClutterCairoTexture::draw signal
|
|
* to be emitted.
|
|
*
|
|
* See also: clutter_cairo_texture_invalidate_rectangle()
|
|
*
|
|
* Since: 1.8
|
|
*/
|
|
void
|
|
clutter_cairo_texture_invalidate (ClutterCairoTexture *self)
|
|
{
|
|
g_return_if_fail (CLUTTER_IS_CAIRO_TEXTURE (self));
|
|
|
|
clutter_cairo_texture_invalidate_rectangle (self, NULL);
|
|
}
|
|
|
|
/**
|
|
* clutter_cairo_texture_create:
|
|
* @self: a #ClutterCairoTexture
|
|
*
|
|
* Creates a new Cairo context for the @cairo texture. It is
|
|
* similar to using clutter_cairo_texture_create_region() with @x_offset
|
|
* and @y_offset of 0, @width equal to the @cairo texture surface width
|
|
* and @height equal to the @cairo texture surface height.
|
|
*
|
|
* <warning><para>Do not call this function within the paint virtual
|
|
* function or from a callback to the #ClutterActor::paint
|
|
* signal.</para></warning>
|
|
*
|
|
* Return value: a newly created Cairo context. Use cairo_destroy()
|
|
* to upload the contents of the context when done drawing
|
|
*
|
|
* Since: 1.0
|
|
*
|
|
* Deprecated: 1.8: Use the #ClutterCairoTexture::draw signal and
|
|
* the clutter_cairo_texture_invalidate() function to obtain a
|
|
* Cairo context for 2D drawing.
|
|
*/
|
|
cairo_t *
|
|
clutter_cairo_texture_create (ClutterCairoTexture *self)
|
|
{
|
|
g_return_val_if_fail (CLUTTER_IS_CAIRO_TEXTURE (self), NULL);
|
|
|
|
clutter_warn_if_paint_fail (self);
|
|
|
|
return clutter_cairo_texture_create_region (self, 0, 0, -1, -1);
|
|
}
|
|
|
|
/**
|
|
* clutter_cairo_set_source_color:
|
|
* @cr: a Cairo context
|
|
* @color: a #ClutterColor
|
|
*
|
|
* Utility function for setting the source color of @cr using
|
|
* a #ClutterColor. This function is the equivalent of:
|
|
*
|
|
* |[
|
|
* cairo_set_source_rgba (cr,
|
|
* color->red / 255.0,
|
|
* color->green / 255.0,
|
|
* color->blue / 255.0,
|
|
* color->alpha / 255.0);
|
|
* ]|
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_cairo_set_source_color (cairo_t *cr,
|
|
const ClutterColor *color)
|
|
{
|
|
g_return_if_fail (cr != NULL);
|
|
g_return_if_fail (color != NULL);
|
|
|
|
if (color->alpha == 0xff)
|
|
cairo_set_source_rgb (cr,
|
|
color->red / 255.0,
|
|
color->green / 255.0,
|
|
color->blue / 255.0);
|
|
else
|
|
cairo_set_source_rgba (cr,
|
|
color->red / 255.0,
|
|
color->green / 255.0,
|
|
color->blue / 255.0,
|
|
color->alpha / 255.0);
|
|
}
|
|
|
|
/**
|
|
* clutter_cairo_texture_set_surface_size:
|
|
* @self: a #ClutterCairoTexture
|
|
* @width: the new width of the surface
|
|
* @height: the new height of the surface
|
|
*
|
|
* Resizes the Cairo surface used by @self to @width and @height.
|
|
*
|
|
* This function will not invalidate the contents of the Cairo
|
|
* texture: you will have to explicitly call either
|
|
* clutter_cairo_texture_invalidate_rectangle() or
|
|
* clutter_cairo_texture_invalidate().
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_cairo_texture_set_surface_size (ClutterCairoTexture *self,
|
|
guint width,
|
|
guint height)
|
|
{
|
|
ClutterCairoTexturePrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_CAIRO_TEXTURE (self));
|
|
|
|
priv = self->priv;
|
|
|
|
if (width == priv->surface_width &&
|
|
height == priv->surface_height)
|
|
return;
|
|
|
|
g_object_freeze_notify (G_OBJECT (self));
|
|
|
|
if (priv->surface_width != width)
|
|
{
|
|
priv->surface_width = width;
|
|
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_SURFACE_WIDTH]);
|
|
}
|
|
|
|
if (priv->surface_height != height)
|
|
{
|
|
priv->surface_height = height;
|
|
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_SURFACE_HEIGHT]);
|
|
}
|
|
|
|
clutter_cairo_texture_surface_resize_internal (self);
|
|
|
|
g_object_thaw_notify (G_OBJECT (self));
|
|
}
|
|
|
|
/**
|
|
* clutter_cairo_texture_get_surface_size:
|
|
* @self: a #ClutterCairoTexture
|
|
* @width: (out): return location for the surface width, or %NULL
|
|
* @height: (out): return location for the surface height, or %NULL
|
|
*
|
|
* Retrieves the surface width and height for @self.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_cairo_texture_get_surface_size (ClutterCairoTexture *self,
|
|
guint *width,
|
|
guint *height)
|
|
{
|
|
g_return_if_fail (CLUTTER_IS_CAIRO_TEXTURE (self));
|
|
|
|
if (width)
|
|
*width = self->priv->surface_width;
|
|
|
|
if (height)
|
|
*height = self->priv->surface_height;
|
|
}
|
|
|
|
/**
|
|
* clutter_cairo_texture_clear:
|
|
* @self: a #ClutterCairoTexture
|
|
*
|
|
* Clears @self's internal drawing surface, so that the next upload
|
|
* will replace the previous contents of the #ClutterCairoTexture
|
|
* rather than adding to it.
|
|
*
|
|
* Calling this function from within a #ClutterCairoTexture::draw
|
|
* signal handler will clear the invalidated area.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_cairo_texture_clear (ClutterCairoTexture *self)
|
|
{
|
|
ClutterCairoTexturePrivate *priv;
|
|
cairo_t *cr;
|
|
|
|
g_return_if_fail (CLUTTER_IS_CAIRO_TEXTURE (self));
|
|
|
|
priv = self->priv;
|
|
|
|
/* if we got called outside of a ::draw signal handler
|
|
* then we clear the whole surface by creating a temporary
|
|
* cairo_t; otherwise, we clear the current cairo_t, which
|
|
* will take into account the clip region.
|
|
*/
|
|
if (priv->cr_context == NULL)
|
|
{
|
|
cairo_surface_t *surface;
|
|
|
|
surface = get_surface (self);
|
|
|
|
cr = cairo_create (surface);
|
|
}
|
|
else
|
|
cr = priv->cr_context;
|
|
|
|
cairo_save (cr);
|
|
|
|
cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
|
|
cairo_paint (cr);
|
|
|
|
cairo_restore (cr);
|
|
|
|
if (priv->cr_context == NULL)
|
|
cairo_destroy (cr);
|
|
}
|
|
|
|
/**
|
|
* clutter_cairo_texture_set_auto_resize:
|
|
* @self: a #ClutterCairoTexture
|
|
* @value: %TRUE if the #ClutterCairoTexture should bind the surface
|
|
* size to the allocation
|
|
*
|
|
* Sets whether the #ClutterCairoTexture should ensure that the
|
|
* backing Cairo surface used matches the allocation assigned to
|
|
* the actor. If the allocation changes, the contents of the
|
|
* #ClutterCairoTexture will also be invalidated automatically.
|
|
*
|
|
* Since: 1.8
|
|
*/
|
|
void
|
|
clutter_cairo_texture_set_auto_resize (ClutterCairoTexture *self,
|
|
gboolean value)
|
|
{
|
|
ClutterCairoTexturePrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_CAIRO_TEXTURE (self));
|
|
|
|
value = !!value;
|
|
|
|
priv = self->priv;
|
|
|
|
if (priv->auto_resize == value)
|
|
return;
|
|
|
|
priv->auto_resize = value;
|
|
|
|
clutter_actor_queue_relayout (CLUTTER_ACTOR (self));
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_AUTO_RESIZE]);
|
|
}
|
|
|
|
/**
|
|
* clutter_cairo_texture_get_auto_resize:
|
|
* @self: a #ClutterCairoTexture
|
|
*
|
|
* Retrieves the value set using clutter_cairo_texture_set_auto_resize().
|
|
*
|
|
* Return value: %TRUE if the #ClutterCairoTexture should track the
|
|
* allocation, and %FALSE otherwise
|
|
*
|
|
* Since: 1.8
|
|
*/
|
|
gboolean
|
|
clutter_cairo_texture_get_auto_resize (ClutterCairoTexture *self)
|
|
{
|
|
g_return_val_if_fail (CLUTTER_IS_CAIRO_TEXTURE (self), FALSE);
|
|
|
|
return self->priv->auto_resize;
|
|
}
|