/* ev-transition-animation.c * this file is part of atril, a mate document viewer * * Copyright (C) 2007 Carlos Garnacho <carlos@imendio.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include <cairo.h> #include <gdk/gdk.h> #include "ev-transition-animation.h" #include "ev-timeline.h" #define N_BLINDS 6 typedef struct EvTransitionAnimationPrivate EvTransitionAnimationPrivate; struct EvTransitionAnimationPrivate { EvTransitionEffect *effect; cairo_surface_t *origin_surface; cairo_surface_t *dest_surface; }; enum { PROP_0, PROP_EFFECT, PROP_ORIGIN_SURFACE, PROP_DEST_SURFACE }; G_DEFINE_TYPE_WITH_PRIVATE (EvTransitionAnimation, ev_transition_animation, EV_TYPE_TIMELINE) static void ev_transition_animation_init (EvTransitionAnimation *animation) { } static void ev_transition_animation_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { EvTransitionAnimationPrivate *priv; priv = ev_transition_animation_get_instance_private (EV_TRANSITION_ANIMATION (object)); switch (prop_id) { case PROP_EFFECT: if (priv->effect) g_object_unref (priv->effect); priv->effect = g_value_dup_object (value); break; case PROP_ORIGIN_SURFACE: ev_transition_animation_set_origin_surface (EV_TRANSITION_ANIMATION (object), g_value_get_pointer (value)); break; case PROP_DEST_SURFACE: ev_transition_animation_set_dest_surface (EV_TRANSITION_ANIMATION (object), g_value_get_pointer (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void ev_transition_animation_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { EvTransitionAnimationPrivate *priv; priv = ev_transition_animation_get_instance_private (EV_TRANSITION_ANIMATION (object)); switch (prop_id) { case PROP_EFFECT: g_value_set_object (value, priv->effect); break; case PROP_ORIGIN_SURFACE: g_value_set_pointer (value, priv->origin_surface); break; case PROP_DEST_SURFACE: g_value_set_pointer (value, priv->dest_surface); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void ev_transition_animation_finalize (GObject *object) { EvTransitionAnimationPrivate *priv; priv = ev_transition_animation_get_instance_private (EV_TRANSITION_ANIMATION (object)); if (priv->effect) g_object_unref (priv->effect); if (priv->origin_surface) cairo_surface_destroy (priv->origin_surface); if (priv->dest_surface) cairo_surface_destroy (priv->dest_surface); G_OBJECT_CLASS (ev_transition_animation_parent_class)->finalize (object); } static GObject * ev_transition_animation_constructor (GType type, guint n_construct_properties, GObjectConstructParam *construct_params) { GObject *object; EvTransitionAnimationPrivate *priv; EvTransitionEffect *effect; gint duration; object = G_OBJECT_CLASS (ev_transition_animation_parent_class)->constructor (type, n_construct_properties, construct_params); priv = ev_transition_animation_get_instance_private (EV_TRANSITION_ANIMATION (object)); effect = priv->effect; g_object_get (effect, "duration", &duration, NULL); ev_timeline_set_duration (EV_TIMELINE (object), duration * 1000); return object; } static void ev_transition_animation_class_init (EvTransitionAnimationClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->set_property = ev_transition_animation_set_property; object_class->get_property = ev_transition_animation_get_property; object_class->finalize = ev_transition_animation_finalize; object_class->constructor = ev_transition_animation_constructor; g_object_class_install_property (object_class, PROP_EFFECT, g_param_spec_object ("effect", "Effect", "Transition effect description", EV_TYPE_TRANSITION_EFFECT, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_ORIGIN_SURFACE, g_param_spec_pointer ("origin-surface", "Origin surface", "Cairo surface from which the animation will happen", G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_DEST_SURFACE, g_param_spec_pointer ("dest-surface", "Destination surface", "Cairo surface to which the animation will happen", G_PARAM_READWRITE)); } static void paint_surface (cairo_t *cr, cairo_surface_t *surface, gdouble x_offset, gdouble y_offset, gdouble alpha, GdkRectangle page_area) { cairo_save (cr); gdk_cairo_rectangle (cr, &page_area); cairo_clip (cr); cairo_surface_set_device_offset (surface, x_offset, y_offset); cairo_set_source_surface (cr, surface, 0, 0); if (alpha == 1.) cairo_paint (cr); else cairo_paint_with_alpha (cr, alpha); cairo_restore (cr); } /* animations */ static void ev_transition_animation_split (cairo_t *cr, EvTransitionAnimation *animation, EvTransitionEffect *effect, gdouble progress, GdkRectangle page_area) { EvTransitionAnimationPrivate *priv; EvTransitionEffectAlignment alignment; EvTransitionEffectDirection direction; gint width, height; priv = ev_transition_animation_get_instance_private (animation); width = page_area.width; height = page_area.height; g_object_get (effect, "alignment", &alignment, "direction", &direction, NULL); if (direction == EV_TRANSITION_DIRECTION_INWARD) { paint_surface (cr, priv->dest_surface, 0, 0, 1., page_area); if (alignment == EV_TRANSITION_ALIGNMENT_HORIZONTAL) { cairo_rectangle (cr, 0, height * progress / 2, width, height * (1 - progress)); } else { cairo_rectangle (cr, width * progress / 2, 0, width * (1 - progress), height); } cairo_clip (cr); paint_surface (cr, priv->origin_surface, 0, 0, 1., page_area); } else { paint_surface (cr, priv->origin_surface, 0, 0, 1., page_area); if (alignment == EV_TRANSITION_ALIGNMENT_HORIZONTAL) { cairo_rectangle (cr, 0, (height / 2) - (height * progress / 2), width, height * progress); } else { cairo_rectangle (cr, (width / 2) - (width * progress / 2), 0, width * progress, height); } cairo_clip (cr); paint_surface (cr, priv->dest_surface, 0, 0, 1., page_area); } } static void ev_transition_animation_blinds (cairo_t *cr, EvTransitionAnimation *animation, EvTransitionEffect *effect, gdouble progress, GdkRectangle page_area) { EvTransitionAnimationPrivate *priv; EvTransitionEffectAlignment alignment; gint width, height, i; priv = ev_transition_animation_get_instance_private (animation); width = page_area.width; height = page_area.height; g_object_get (effect, "alignment", &alignment, NULL); paint_surface (cr, priv->origin_surface, 0, 0, 1., page_area); for (i = 0; i < N_BLINDS; i++) { cairo_save (cr); if (alignment == EV_TRANSITION_ALIGNMENT_HORIZONTAL) { cairo_rectangle (cr, 0, height / N_BLINDS * i, width, height / N_BLINDS * progress); } else { cairo_rectangle (cr, width / N_BLINDS * i, 0, width / N_BLINDS * progress, height); } cairo_clip (cr); paint_surface (cr, priv->dest_surface, 0, 0, 1., page_area); cairo_restore (cr); } } static void ev_transition_animation_box (cairo_t *cr, EvTransitionAnimation *animation, EvTransitionEffect *effect, gdouble progress, GdkRectangle page_area) { EvTransitionAnimationPrivate *priv; EvTransitionEffectDirection direction; gint width, height; priv = ev_transition_animation_get_instance_private (animation); width = page_area.width; height = page_area.height; g_object_get (effect, "direction", &direction, NULL); if (direction == EV_TRANSITION_DIRECTION_INWARD) { paint_surface (cr, priv->dest_surface, 0, 0, 1., page_area); cairo_rectangle (cr, width * progress / 2, height * progress / 2, width * (1 - progress), height * (1 - progress)); cairo_clip (cr); paint_surface (cr, priv->origin_surface, 0, 0, 1., page_area); } else { paint_surface (cr, priv->origin_surface, 0, 0, 1., page_area); cairo_rectangle (cr, (width / 2) - (width * progress / 2), (height / 2) - (height * progress / 2), width * progress, height * progress); cairo_clip (cr); paint_surface (cr, priv->dest_surface, 0, 0, 1., page_area); } } static void ev_transition_animation_wipe (cairo_t *cr, EvTransitionAnimation *animation, EvTransitionEffect *effect, gdouble progress, GdkRectangle page_area) { EvTransitionAnimationPrivate *priv; gint width, height; gint angle; priv = ev_transition_animation_get_instance_private (animation); width = page_area.width; height = page_area.height; g_object_get (effect, "angle", &angle, NULL); paint_surface (cr, priv->origin_surface, 0, 0, 1., page_area); if (angle == 0) { /* left to right */ cairo_rectangle (cr, 0, 0, width * progress, height); } else if (angle <= 90) { /* bottom to top */ cairo_rectangle (cr, 0, height * (1 - progress), width, height * progress); } else if (angle <= 180) { /* right to left */ cairo_rectangle (cr, width * (1 - progress), 0, width * progress, height); } else if (angle <= 270) { /* top to bottom */ cairo_rectangle (cr, 0, 0, width, height * progress); } cairo_clip (cr); paint_surface (cr, priv->dest_surface, 0, 0, 1., page_area); } static void ev_transition_animation_dissolve (cairo_t *cr, EvTransitionAnimation *animation, EvTransitionEffect *effect, gdouble progress, GdkRectangle page_area) { EvTransitionAnimationPrivate *priv; priv = ev_transition_animation_get_instance_private (animation); paint_surface (cr, priv->dest_surface, 0, 0, 1., page_area); paint_surface (cr, priv->origin_surface, 0, 0, 1 - progress, page_area); } static void ev_transition_animation_push (cairo_t *cr, EvTransitionAnimation *animation, EvTransitionEffect *effect, gdouble progress, GdkRectangle page_area) { EvTransitionAnimationPrivate *priv; gint width, height; gint angle; priv = ev_transition_animation_get_instance_private (animation); width = page_area.width; height = page_area.height; g_object_get (effect, "angle", &angle, NULL); if (angle == 0) { /* left to right */ paint_surface (cr, priv->origin_surface, - (width * progress), 0, 1., page_area); paint_surface (cr, priv->dest_surface, width * (1 - progress), 0, 1., page_area); } else { /* top to bottom */ paint_surface (cr, priv->origin_surface, 0, - (height * progress), 1., page_area); paint_surface (cr, priv->dest_surface, 0, height * (1 - progress), 1., page_area); } } static void ev_transition_animation_cover (cairo_t *cr, EvTransitionAnimation *animation, EvTransitionEffect *effect, gdouble progress, GdkRectangle page_area) { EvTransitionAnimationPrivate *priv; gint width, height; gint angle; priv = ev_transition_animation_get_instance_private (animation); width = page_area.width; height = page_area.height; g_object_get (effect, "angle", &angle, NULL); paint_surface (cr, priv->origin_surface, 0, 0, 1., page_area); if (angle == 0) { /* left to right */ paint_surface (cr, priv->dest_surface, width * (1 - progress), 0, 1., page_area); } else { /* top to bottom */ paint_surface (cr, priv->dest_surface, 0, height * (1 - progress), 1., page_area); } } static void ev_transition_animation_uncover (cairo_t *cr, EvTransitionAnimation *animation, EvTransitionEffect *effect, gdouble progress, GdkRectangle page_area) { EvTransitionAnimationPrivate *priv; gint width, height; gint angle; priv = ev_transition_animation_get_instance_private (animation); width = page_area.width; height = page_area.height; g_object_get (effect, "angle", &angle, NULL); paint_surface (cr, priv->dest_surface, 0, 0, 1., page_area); if (angle == 0) { /* left to right */ paint_surface (cr, priv->origin_surface, - (width * progress), 0, 1., page_area); } else { /* top to bottom */ paint_surface (cr, priv->origin_surface, 0, - (height * progress), 1., page_area); } } static void ev_transition_animation_fade (cairo_t *cr, EvTransitionAnimation *animation, EvTransitionEffect *effect, gdouble progress, GdkRectangle page_area) { EvTransitionAnimationPrivate *priv; priv = ev_transition_animation_get_instance_private (animation); paint_surface (cr, priv->origin_surface, 0, 0, 1., page_area); paint_surface (cr, priv->dest_surface, 0, 0, progress, page_area); } void ev_transition_animation_paint (EvTransitionAnimation *animation, cairo_t *cr, GdkRectangle page_area) { EvTransitionAnimationPrivate *priv; EvTransitionEffectType type; gdouble progress; g_return_if_fail (EV_IS_TRANSITION_ANIMATION (animation)); priv = ev_transition_animation_get_instance_private (animation); if (!priv->dest_surface) { /* animation is still not ready, paint the origin surface */ paint_surface (cr, priv->origin_surface, 0, 0, 1., page_area); return; } g_object_get (priv->effect, "type", &type, NULL); progress = ev_timeline_get_progress (EV_TIMELINE (animation)); switch (type) { case EV_TRANSITION_EFFECT_REPLACE: /* just paint the destination slide */ paint_surface (cr, priv->dest_surface, 0, 0, 1., page_area); break; case EV_TRANSITION_EFFECT_SPLIT: ev_transition_animation_split (cr, animation, priv->effect, progress, page_area); break; case EV_TRANSITION_EFFECT_BLINDS: ev_transition_animation_blinds (cr, animation, priv->effect, progress, page_area); break; case EV_TRANSITION_EFFECT_BOX: ev_transition_animation_box (cr, animation, priv->effect, progress, page_area); break; case EV_TRANSITION_EFFECT_WIPE: ev_transition_animation_wipe (cr, animation, priv->effect, progress, page_area); break; case EV_TRANSITION_EFFECT_DISSOLVE: ev_transition_animation_dissolve (cr, animation, priv->effect, progress, page_area); break; case EV_TRANSITION_EFFECT_PUSH: ev_transition_animation_push (cr, animation, priv->effect, progress, page_area); break; case EV_TRANSITION_EFFECT_COVER: ev_transition_animation_cover (cr, animation, priv->effect, progress, page_area); break; case EV_TRANSITION_EFFECT_UNCOVER: ev_transition_animation_uncover (cr, animation, priv->effect, progress, page_area); break; case EV_TRANSITION_EFFECT_FADE: ev_transition_animation_fade (cr, animation, priv->effect, progress, page_area); break; default: { GEnumValue *enum_value; enum_value = g_enum_get_value (g_type_class_peek (EV_TYPE_TRANSITION_EFFECT_TYPE), type); g_warning ("Unimplemented transition animation: '%s', " "please post a bug report on Atril bug tracker " "(https://github.com/mate-desktop/atril/issues) with a testcase.", enum_value->value_nick); /* just paint the destination slide */ paint_surface (cr, priv->dest_surface, 0, 0, 1., page_area); } } } EvTransitionAnimation * ev_transition_animation_new (EvTransitionEffect *effect) { g_return_val_if_fail (EV_IS_TRANSITION_EFFECT (effect), NULL); return g_object_new (EV_TYPE_TRANSITION_ANIMATION, "effect", effect, NULL); } void ev_transition_animation_set_origin_surface (EvTransitionAnimation *animation, cairo_surface_t *origin_surface) { EvTransitionAnimationPrivate *priv; cairo_surface_t *surface; g_return_if_fail (EV_IS_TRANSITION_ANIMATION (animation)); priv = ev_transition_animation_get_instance_private (animation); if (priv->origin_surface == origin_surface) return; surface = cairo_surface_reference (origin_surface); if (priv->origin_surface) cairo_surface_destroy (priv->origin_surface); priv->origin_surface = surface; g_object_notify (G_OBJECT (animation), "origin-surface"); if (priv->origin_surface && priv->dest_surface) ev_timeline_start (EV_TIMELINE (animation)); } void ev_transition_animation_set_dest_surface (EvTransitionAnimation *animation, cairo_surface_t *dest_surface) { EvTransitionAnimationPrivate *priv; cairo_surface_t *surface; g_return_if_fail (EV_IS_TRANSITION_ANIMATION (animation)); priv = ev_transition_animation_get_instance_private (animation); if (priv->dest_surface == dest_surface) return; surface = cairo_surface_reference (dest_surface); if (priv->dest_surface) cairo_surface_destroy (priv->dest_surface); priv->dest_surface = surface; g_object_notify (G_OBJECT (animation), "dest-surface"); if (priv->origin_surface && priv->dest_surface) ev_timeline_start (EV_TIMELINE (animation)); } gboolean ev_transition_animation_ready (EvTransitionAnimation *animation) { EvTransitionAnimationPrivate *priv; g_return_val_if_fail (EV_IS_TRANSITION_ANIMATION (animation), FALSE); priv = ev_transition_animation_get_instance_private (animation); return (priv->origin_surface != NULL); }