/* * Clutter. * * An OpenGL based 'interactive canvas' library. * * Copyright (C) 2010 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-effect * @short_description: Base class for actor effects * * The #ClutterEffect class provides a default type and API for creating * effects for generic actors. * * Effects are a #ClutterActorMeta sub-class that modify the way an actor * is painted in a way that is not part of the actor's implementation. * * Effects should be the preferred way to affect the paint sequence of an * actor without sub-classing the actor itself and overriding the * #ClutterActorClass.paint()_ virtual function. * * ## Implementing a ClutterEffect * * Creating a sub-class of #ClutterEffect requires overriding the * #ClutterEffectClass.paint() method. The implementation of the function should look * something like this: * * |[ * void effect_paint (ClutterEffect *effect, ClutterEffectPaintFlags flags) * { * // Set up initialisation of the paint such as binding a * // CoglOffscreen or other operations * * // Chain to the next item in the paint sequence. This will either call * // ‘paint’ on the next effect or just paint the actor if this is * // the last effect. * ClutterActor *actor = * clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (effect)); * * clutter_actor_continue_paint (actor); * * // perform any cleanup of state, such as popping the CoglOffscreen * } * ]| * * The effect can optionally avoid calling clutter_actor_continue_paint() to skip any * further stages of the paint sequence. This is useful for example if the effect * contains a cached image of the actor. In that case it can optimise painting by * avoiding the actor paint and instead painting the cached image. * * The %CLUTTER_EFFECT_PAINT_ACTOR_DIRTY flag is useful in this case. Clutter will set * this flag when a redraw has been queued on the actor since it was last painted. The * effect can use this information to decide if the cached image is still valid. * * ## A simple ClutterEffect implementation * * The example below creates two rectangles: one will be painted "behind" the actor, * while another will be painted "on top" of the actor. * * The #ClutterActorMetaClass.set_actor() implementation will create the two materials * used for the two different rectangles; the #ClutterEffectClass.paint() implementation * will paint the first material using cogl_rectangle(), before continuing and then it * will paint paint the second material after. * * |[ * typedef struct { * ClutterEffect parent_instance; * * CoglHandle rect_1; * CoglHandle rect_2; * } MyEffect; * * typedef struct _ClutterEffectClass MyEffectClass; * * G_DEFINE_TYPE (MyEffect, my_effect, CLUTTER_TYPE_EFFECT); * * static void * my_effect_set_actor (ClutterActorMeta *meta, * ClutterActor *actor) * { * MyEffect *self = MY_EFFECT (meta); * * // Clear the previous state // * if (self->rect_1) * { * cogl_handle_unref (self->rect_1); * self->rect_1 = NULL; * } * * if (self->rect_2) * { * cogl_handle_unref (self->rect_2); * self->rect_2 = NULL; * } * * // Maintain a pointer to the actor * self->actor = actor; * * // If we've been detached by the actor then we should just bail out here * if (self->actor == NULL) * return; * * // Create a red material * self->rect_1 = cogl_material_new (); * cogl_material_set_color4f (self->rect_1, 1.0, 0.0, 0.0, 1.0); * * // Create a green material * self->rect_2 = cogl_material_new (); * cogl_material_set_color4f (self->rect_2, 0.0, 1.0, 0.0, 1.0); * } * * static gboolean * my_effect_paint (ClutterEffect *effect) * { * MyEffect *self = MY_EFFECT (effect); * gfloat width, height; * * clutter_actor_get_size (self->actor, &width, &height); * * // Paint the first rectangle in the upper left quadrant * cogl_set_source (self->rect_1); * cogl_rectangle (0, 0, width / 2, height / 2); * * // Continue to the rest of the paint sequence * clutter_actor_continue_paint (self->actor); * * // Paint the second rectangle in the lower right quadrant * cogl_set_source (self->rect_2); * cogl_rectangle (width / 2, height / 2, width, height); * } * * static void * my_effect_class_init (MyEffectClass *klass) * { * ClutterActorMetaClas *meta_class = CLUTTER_ACTOR_META_CLASS (klass); * * meta_class->set_actor = my_effect_set_actor; * * klass->paint = my_effect_paint; * } * ]| * * #ClutterEffect is available since Clutter 1.4 */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "clutter-effect.h" #include "clutter-actor-meta-private.h" #include "clutter-debug.h" #include "clutter-effect-private.h" #include "clutter-enum-types.h" #include "clutter-marshal.h" #include "clutter-private.h" #include "clutter-actor-private.h" G_DEFINE_ABSTRACT_TYPE (ClutterEffect, clutter_effect, CLUTTER_TYPE_ACTOR_META); static gboolean clutter_effect_real_pre_paint (ClutterEffect *effect) { return TRUE; } static void clutter_effect_real_post_paint (ClutterEffect *effect) { } static gboolean clutter_effect_real_get_paint_volume (ClutterEffect *effect, ClutterPaintVolume *volume) { return TRUE; } static void clutter_effect_real_paint (ClutterEffect *effect, ClutterEffectPaintFlags flags) { ClutterActorMeta *actor_meta = CLUTTER_ACTOR_META (effect); ClutterActor *actor; gboolean pre_paint_succeeded; /* The default implementation provides a compatibility wrapper for effects that haven't migrated to use the 'paint' virtual yet. This just calls the old pre and post virtuals before chaining on */ pre_paint_succeeded = _clutter_effect_pre_paint (effect); actor = clutter_actor_meta_get_actor (actor_meta); clutter_actor_continue_paint (actor); if (pre_paint_succeeded) _clutter_effect_post_paint (effect); } static void clutter_effect_real_pick (ClutterEffect *effect, ClutterEffectPaintFlags flags) { ClutterActorMeta *actor_meta = CLUTTER_ACTOR_META (effect); ClutterActor *actor; actor = clutter_actor_meta_get_actor (actor_meta); clutter_actor_continue_paint (actor); } static void clutter_effect_notify (GObject *gobject, GParamSpec *pspec) { if (strcmp (pspec->name, "enabled") == 0) { ClutterActorMeta *meta = CLUTTER_ACTOR_META (gobject); ClutterActor *actor = clutter_actor_meta_get_actor (meta); if (actor != NULL) clutter_actor_queue_redraw (actor); } if (G_OBJECT_CLASS (clutter_effect_parent_class)->notify != NULL) G_OBJECT_CLASS (clutter_effect_parent_class)->notify (gobject, pspec); } static void clutter_effect_class_init (ClutterEffectClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->notify = clutter_effect_notify; klass->pre_paint = clutter_effect_real_pre_paint; klass->post_paint = clutter_effect_real_post_paint; klass->get_paint_volume = clutter_effect_real_get_paint_volume; klass->paint = clutter_effect_real_paint; klass->pick = clutter_effect_real_pick; } static void clutter_effect_init (ClutterEffect *self) { } gboolean _clutter_effect_pre_paint (ClutterEffect *effect) { g_return_val_if_fail (CLUTTER_IS_EFFECT (effect), FALSE); return CLUTTER_EFFECT_GET_CLASS (effect)->pre_paint (effect); } void _clutter_effect_post_paint (ClutterEffect *effect) { g_return_if_fail (CLUTTER_IS_EFFECT (effect)); CLUTTER_EFFECT_GET_CLASS (effect)->post_paint (effect); } void _clutter_effect_paint (ClutterEffect *effect, ClutterEffectPaintFlags flags) { g_return_if_fail (CLUTTER_IS_EFFECT (effect)); CLUTTER_EFFECT_GET_CLASS (effect)->paint (effect, flags); } void _clutter_effect_pick (ClutterEffect *effect, ClutterEffectPaintFlags flags) { g_return_if_fail (CLUTTER_IS_EFFECT (effect)); CLUTTER_EFFECT_GET_CLASS (effect)->pick (effect, flags); } gboolean _clutter_effect_get_paint_volume (ClutterEffect *effect, ClutterPaintVolume *volume) { g_return_val_if_fail (CLUTTER_IS_EFFECT (effect), FALSE); g_return_val_if_fail (volume != NULL, FALSE); return CLUTTER_EFFECT_GET_CLASS (effect)->get_paint_volume (effect, volume); } /** * clutter_effect_queue_repaint: * @effect: A #ClutterEffect which needs redrawing * * Queues a repaint of the effect. The effect can detect when the ‘paint’ * method is called as a result of this function because it will not * have the %CLUTTER_EFFECT_PAINT_ACTOR_DIRTY flag set. In that case the * effect is free to assume that the actor has not changed its * appearance since the last time it was painted so it doesn't need to * call clutter_actor_continue_paint() if it can draw a cached * image. This is mostly intended for effects that are using a * %CoglOffscreen to redirect the actor (such as * %ClutterOffscreenEffect). In that case the effect can save a bit of * rendering time by painting the cached texture without causing the * entire actor to be painted. * * This function can be used by effects that have their own animatable * parameters. For example, an effect which adds a varying degree of a * red tint to an actor by redirecting it through a CoglOffscreen * might have a property to specify the level of tint. When this value * changes, the underlying actor doesn't need to be redrawn so the * effect can call clutter_effect_queue_repaint() to make sure the * effect is repainted. * * Note however that modifying the position of the parent of an actor * may change the appearance of the actor because its transformation * matrix would change. In this case a redraw wouldn't be queued on * the actor itself so the %CLUTTER_EFFECT_PAINT_ACTOR_DIRTY would still * not be set. The effect can detect this case by keeping track of the * last modelview matrix that was used to render the actor and * veryifying that it remains the same in the next paint. * * Any other effects that are layered on top of the passed in effect * will still be passed the %CLUTTER_EFFECT_PAINT_ACTOR_DIRTY flag. If * anything queues a redraw on the actor without specifying an effect * or with an effect that is lower in the chain of effects than this * one then that will override this call. In that case this effect * will instead be called with the %CLUTTER_EFFECT_PAINT_ACTOR_DIRTY * flag set. * * Since: 1.8 */ void clutter_effect_queue_repaint (ClutterEffect *effect) { ClutterActor *actor; g_return_if_fail (CLUTTER_IS_EFFECT (effect)); actor = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (effect)); /* If the effect has no actor then nothing needs to be done */ if (actor != NULL) _clutter_actor_queue_redraw_full (actor, 0, /* flags */ NULL, /* clip volume */ effect /* effect */); }