/* * 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 */ /** * ClutterTransition: * * Transition between two values * * #ClutterTransition is an abstract subclass of [class@Timeline] that * computes the interpolation between two values, stored by a [class@Interval]. */ #include "clutter/clutter-build-config.h" #include "clutter/clutter-transition.h" #include "clutter/clutter-animatable.h" #include "clutter/clutter-debug.h" #include "clutter/clutter-interval.h" #include "clutter/clutter-private.h" #include "clutter/clutter-timeline.h" #include struct _ClutterTransitionPrivate { ClutterInterval *interval; ClutterAnimatable *animatable; guint remove_on_complete : 1; }; enum { PROP_0, PROP_INTERVAL, PROP_ANIMATABLE, PROP_REMOVE_ON_COMPLETE, PROP_LAST }; static GParamSpec *obj_props[PROP_LAST] = { NULL, }; static GQuark quark_animatable_set = 0; G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (ClutterTransition, clutter_transition, CLUTTER_TYPE_TIMELINE) static void clutter_transition_attach (ClutterTransition *transition, ClutterAnimatable *animatable) { CLUTTER_TRANSITION_GET_CLASS (transition)->attached (transition, animatable); } static void clutter_transition_detach (ClutterTransition *transition, ClutterAnimatable *animatable) { CLUTTER_TRANSITION_GET_CLASS (transition)->detached (transition, animatable); } static void clutter_transition_real_compute_value (ClutterTransition *transition, ClutterAnimatable *animatable, ClutterInterval *interval, gdouble progress) { } static void clutter_transition_real_attached (ClutterTransition *transition, ClutterAnimatable *animatable) { } static void clutter_transition_real_detached (ClutterTransition *transition, ClutterAnimatable *animatable) { } static void clutter_transition_new_frame (ClutterTimeline *timeline, gint elapsed G_GNUC_UNUSED) { ClutterTransition *transition = CLUTTER_TRANSITION (timeline); ClutterTransitionPrivate *priv = transition->priv; gdouble progress; if (priv->interval == NULL || priv->animatable == NULL) return; progress = clutter_timeline_get_progress (timeline); CLUTTER_TRANSITION_GET_CLASS (timeline)->compute_value (transition, priv->animatable, priv->interval, progress); } static void clutter_transition_stopped (ClutterTimeline *timeline, gboolean is_finished) { ClutterTransitionPrivate *priv = CLUTTER_TRANSITION (timeline)->priv; if (is_finished && priv->animatable != NULL && priv->remove_on_complete) { clutter_transition_detach (CLUTTER_TRANSITION (timeline), priv->animatable); g_clear_object (&priv->animatable); } } static void clutter_transition_set_property (GObject *gobject, guint prop_id, const GValue *value, GParamSpec *pspec) { ClutterTransition *transition = CLUTTER_TRANSITION (gobject); switch (prop_id) { case PROP_INTERVAL: clutter_transition_set_interval (transition, g_value_get_object (value)); break; case PROP_ANIMATABLE: clutter_transition_set_animatable (transition, g_value_get_object (value)); break; case PROP_REMOVE_ON_COMPLETE: clutter_transition_set_remove_on_complete (transition, g_value_get_boolean (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); break; } } static void clutter_transition_get_property (GObject *gobject, guint prop_id, GValue *value, GParamSpec *pspec) { ClutterTransitionPrivate *priv = CLUTTER_TRANSITION (gobject)->priv; switch (prop_id) { case PROP_INTERVAL: g_value_set_object (value, priv->interval); break; case PROP_ANIMATABLE: g_value_set_object (value, priv->animatable); break; case PROP_REMOVE_ON_COMPLETE: g_value_set_boolean (value, priv->remove_on_complete); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); break; } } static void clutter_transition_dispose (GObject *gobject) { ClutterTransitionPrivate *priv = CLUTTER_TRANSITION (gobject)->priv; if (priv->animatable != NULL) clutter_transition_detach (CLUTTER_TRANSITION (gobject), priv->animatable); g_clear_object (&priv->interval); g_clear_object (&priv->animatable); G_OBJECT_CLASS (clutter_transition_parent_class)->dispose (gobject); } static void clutter_transition_class_init (ClutterTransitionClass *klass) { ClutterTimelineClass *timeline_class = CLUTTER_TIMELINE_CLASS (klass); GObjectClass *gobject_class = G_OBJECT_CLASS (klass); quark_animatable_set = g_quark_from_static_string ("-clutter-transition-animatable-set"); klass->compute_value = clutter_transition_real_compute_value; klass->attached = clutter_transition_real_attached; klass->detached = clutter_transition_real_detached; timeline_class->new_frame = clutter_transition_new_frame; timeline_class->stopped = clutter_transition_stopped; gobject_class->set_property = clutter_transition_set_property; gobject_class->get_property = clutter_transition_get_property; gobject_class->dispose = clutter_transition_dispose; /** * ClutterTransition:interval: * * The [class@Interval] used to describe the initial and final states * of the transition. */ obj_props[PROP_INTERVAL] = g_param_spec_object ("interval", NULL, NULL, CLUTTER_TYPE_INTERVAL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); /** * ClutterTransition:animatable: * * The [iface@Animatable] instance currently being animated. */ obj_props[PROP_ANIMATABLE] = g_param_spec_object ("animatable", NULL, NULL, CLUTTER_TYPE_ANIMATABLE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); /** * ClutterTransition:remove-on-complete: * * Whether the #ClutterTransition should be automatically detached * from the [property@Transition:animatable] instance whenever the * [signal@Timeline::stopped] signal is emitted. * * The [property@Transition:remove-on-complete] property takes into * account the value of the [property@Timeline:repeat-count] property, * and it only detaches the transition if the transition is not * repeating. */ obj_props[PROP_REMOVE_ON_COMPLETE] = g_param_spec_boolean ("remove-on-complete", NULL, NULL, FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, PROP_LAST, obj_props); } static void clutter_transition_init (ClutterTransition *self) { self->priv = clutter_transition_get_instance_private (self); } /** * clutter_transition_set_interval: * @transition: a #ClutterTransition * @interval: (allow-none): a #ClutterInterval, or %NULL * * Sets the [property@Transition:interval] property using @interval. * * The @transition will acquire a reference on the @interval, sinking * the floating flag on it if necessary. */ void clutter_transition_set_interval (ClutterTransition *transition, ClutterInterval *interval) { ClutterTransitionPrivate *priv; g_return_if_fail (CLUTTER_IS_TRANSITION (transition)); g_return_if_fail (interval == NULL || CLUTTER_IS_INTERVAL (interval)); priv = transition->priv; if (priv->interval == interval) return; g_clear_object (&priv->interval); if (interval != NULL) priv->interval = g_object_ref_sink (interval); g_object_notify_by_pspec (G_OBJECT (transition), obj_props[PROP_INTERVAL]); } /** * clutter_transition_get_interval: * @transition: a #ClutterTransition * * Retrieves the interval set using [method@Transition.set_interval] * * Return value: (transfer none): a [class@Interval], or %NULL; the returned * interval is owned by the #ClutterTransition and it should not be freed * directly */ ClutterInterval * clutter_transition_get_interval (ClutterTransition *transition) { g_return_val_if_fail (CLUTTER_IS_TRANSITION (transition), NULL); return transition->priv->interval; } /** * clutter_transition_set_animatable: * @transition: a #ClutterTransition * @animatable: (allow-none): a #ClutterAnimatable, or %NULL * * Sets the [property@Transition:animatable] property. * * The @transition will acquire a reference to the @animatable instance, * and will call the [vfunc@Transition.attached] virtual function. * * If an existing [iface@Animatable] is attached to @transition, the * reference will be released, and the [vfunc@Transition.detached] * virtual function will be called. */ void clutter_transition_set_animatable (ClutterTransition *transition, ClutterAnimatable *animatable) { ClutterTransitionPrivate *priv; ClutterActor *actor; g_return_if_fail (CLUTTER_IS_TRANSITION (transition)); g_return_if_fail (animatable == NULL || CLUTTER_IS_ANIMATABLE (animatable)); priv = transition->priv; if (priv->animatable == animatable) return; if (priv->animatable != NULL) clutter_transition_detach (transition, priv->animatable); g_clear_object (&priv->animatable); if (animatable != NULL) { priv->animatable = g_object_ref (animatable); clutter_transition_attach (transition, priv->animatable); } actor = clutter_animatable_get_actor (animatable); clutter_timeline_set_actor (CLUTTER_TIMELINE (transition), actor); } /** * clutter_transition_get_animatable: * @transition: a #ClutterTransition * * Retrieves the [iface@Animatable] set using [method@Transition.set_animatable]. * * Return value: (transfer none): a [iface@Animatable], or %NULL; the returned * animatable is owned by the #ClutterTransition, and it should not be freed * directly. */ ClutterAnimatable * clutter_transition_get_animatable (ClutterTransition *transition) { g_return_val_if_fail (CLUTTER_IS_TRANSITION (transition), NULL); return transition->priv->animatable; } /** * clutter_transition_set_remove_on_complete: * @transition: a #ClutterTransition * @remove_complete: whether to detach @transition when complete * * Sets whether @transition should be detached from the [iface@Animatable] * set using [method@Transition.set_animatable] when the * [signal@Timeline::completed] signal is emitted. */ void clutter_transition_set_remove_on_complete (ClutterTransition *transition, gboolean remove_complete) { g_return_if_fail (CLUTTER_IS_TRANSITION (transition)); remove_complete = !!remove_complete; if (transition->priv->remove_on_complete == remove_complete) return; transition->priv->remove_on_complete = remove_complete; g_object_notify_by_pspec (G_OBJECT (transition), obj_props[PROP_REMOVE_ON_COMPLETE]); } /** * clutter_transition_get_remove_on_complete: * @transition: a #ClutterTransition * * Retrieves the value of the [property@Transition:remove-on-complete] property. * * Return value: %TRUE if the @transition should be detached when complete, * and %FALSE otherwise */ gboolean clutter_transition_get_remove_on_complete (ClutterTransition *transition) { g_return_val_if_fail (CLUTTER_IS_TRANSITION (transition), FALSE); return transition->priv->remove_on_complete; } typedef void (* IntervalSetFunc) (ClutterInterval *interval, const GValue *value); static inline void clutter_transition_set_value (ClutterTransition *transition, IntervalSetFunc interval_set_func, const GValue *value) { ClutterTransitionPrivate *priv = transition->priv; GType interval_type; if (priv->interval == NULL) { priv->interval = clutter_interval_new_with_values (G_VALUE_TYPE (value), NULL, NULL); g_object_ref_sink (priv->interval); } interval_type = clutter_interval_get_value_type (priv->interval); if (!g_type_is_a (G_VALUE_TYPE (value), interval_type)) { if (g_value_type_compatible (G_VALUE_TYPE (value), interval_type)) { interval_set_func (priv->interval, value); return; } if (g_value_type_transformable (G_VALUE_TYPE (value), interval_type)) { GValue transform = G_VALUE_INIT; g_value_init (&transform, interval_type); if (g_value_transform (value, &transform)) interval_set_func (priv->interval, &transform); else { g_warning ("%s: Unable to convert a value of type '%s' into " "the value type '%s' of the interval used by the " "transition.", G_STRLOC, g_type_name (G_VALUE_TYPE (value)), g_type_name (interval_type)); } g_value_unset (&transform); } } else interval_set_func (priv->interval, value); } /** * clutter_transition_set_from_value: (rename-to clutter_transition_set_from) * @transition: a #ClutterTransition * @value: a #GValue with the initial value of the transition * * Sets the initial value of the transition. * * This is a convenience function that will either create the * [class@Interval] used by @transition, or will update it if * the [property@Transition:interval] is already set. * * This function will copy the contents of @value, so it is * safe to call [method@GObject.Value.unset] after it returns. * * If @transition already has a [property@Transition:interval] set, * then @value must hold the same type, or a transformable type, * as the interval's [property@Interval:value-type] property. * * This function is meant to be used by language bindings. */ void clutter_transition_set_from_value (ClutterTransition *transition, const GValue *value) { g_return_if_fail (CLUTTER_IS_TRANSITION (transition)); g_return_if_fail (G_IS_VALUE (value)); clutter_transition_set_value (transition, clutter_interval_set_initial_value, value); } /** * clutter_transition_set_to_value: (rename-to clutter_transition_set_to) * @transition: a #ClutterTransition * @value: a #GValue with the final value of the transition * * Sets the final value of the transition. * * This is a convenience function that will either create the * #ClutterInterval used by @transition, or will update it if * the [property@Transition:interval] is already set. * * This function will copy the contents of @value, so it is * safe to call [method@GObject.Value.unset] after it returns. * * If @transition already has a [property@Transition:interval] set, * then @value must hold the same type, or a transformable type, * as the interval's [property@Interval:value-type] property. * * This function is meant to be used by language bindings. */ void clutter_transition_set_to_value (ClutterTransition *transition, const GValue *value) { g_return_if_fail (CLUTTER_IS_TRANSITION (transition)); g_return_if_fail (G_IS_VALUE (value)); clutter_transition_set_value (transition, clutter_interval_set_final_value, value); } /** * clutter_transition_set_from: (skip) * @transition: a #ClutterTransition * @value_type: the type of the value to set * @...: the initial value * * Sets the initial value of the transition. * * This is a convenience function that will either create the * #ClutterInterval used by @transition, or will update it if * the [property@Transition:interval] is already set. * * If @transition already has a [property@Transition:interval] set, * then @value must hold the same type, or a transformable type, * as the interval's [property@Interval:value-type] property. * * This is a convenience function for the C API; language bindings * should use [method@Transition.set_from_value] instead. */ void clutter_transition_set_from (ClutterTransition *transition, GType value_type, ...) { GValue value = G_VALUE_INIT; gchar *error = NULL; va_list args; g_return_if_fail (CLUTTER_IS_TRANSITION (transition)); g_return_if_fail (value_type != G_TYPE_INVALID); va_start (args, value_type); G_VALUE_COLLECT_INIT (&value, value_type, args, 0, &error); va_end (args); if (error != NULL) { g_warning ("%s: %s", G_STRLOC, error); g_free (error); return; } clutter_transition_set_value (transition, clutter_interval_set_initial_value, &value); g_value_unset (&value); } /** * clutter_transition_set_to: (skip) * @transition: a #ClutterTransition * @value_type: the type of the value to set * @...: the final value * * Sets the final value of the transition. * * This is a convenience function that will either create the * #ClutterInterval used by @transition, or will update it if * the [property@Transition:interval] is already set. * * If @transition already has a [property@Transition:interval] set, * then @value must hold the same type, or a transformable type, * as the interval's [property@Interval:value-type] property. * * This is a convenience function for the C API; language bindings * should use [method@Transition.set_to_value] instead. */ void clutter_transition_set_to (ClutterTransition *transition, GType value_type, ...) { GValue value = G_VALUE_INIT; gchar *error = NULL; va_list args; g_return_if_fail (CLUTTER_IS_TRANSITION (transition)); g_return_if_fail (value_type != G_TYPE_INVALID); va_start (args, value_type); G_VALUE_COLLECT_INIT (&value, value_type, args, 0, &error); va_end (args); if (error != NULL) { g_warning ("%s: %s", G_STRLOC, error); g_free (error); return; } clutter_transition_set_value (transition, clutter_interval_set_final_value, &value); g_value_unset (&value); }