]> www.fi.muni.cz Git - evince.git/blob - libview/ev-transition-animation.c
[libview] Do not scale when drawing animations in presentation mode
[evince.git] / libview / ev-transition-animation.c
1 /* ev-transition-animation.c
2  *  this file is part of evince, a gnome document viewer
3  *
4  * Copyright (C) 2007 Carlos Garnacho <carlos@imendio.com>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  */
21
22 #include <cairo.h>
23 #include <gdk/gdk.h>
24 #include "ev-transition-animation.h"
25 #include "ev-timeline.h"
26
27 #define EV_TRANSITION_ANIMATION_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), EV_TYPE_TRANSITION_ANIMATION, EvTransitionAnimationPriv))
28 #define N_BLINDS 6
29
30 typedef struct EvTransitionAnimationPriv EvTransitionAnimationPriv;
31
32 struct EvTransitionAnimationPriv {
33         EvTransitionEffect *effect;
34         cairo_surface_t *origin_surface;
35         cairo_surface_t *dest_surface;
36 };
37
38 enum {
39         PROP_0,
40         PROP_EFFECT,
41         PROP_ORIGIN_SURFACE,
42         PROP_DEST_SURFACE
43 };
44
45
46 G_DEFINE_TYPE (EvTransitionAnimation, ev_transition_animation, EV_TYPE_TIMELINE)
47
48
49 static void
50 ev_transition_animation_init (EvTransitionAnimation *animation)
51 {
52 }
53
54 static void
55 ev_transition_animation_set_property (GObject      *object,
56                                       guint         prop_id,
57                                       const GValue *value,
58                                       GParamSpec   *pspec)
59 {
60         EvTransitionAnimationPriv *priv;
61
62         priv = EV_TRANSITION_ANIMATION_GET_PRIVATE (object);
63
64         switch (prop_id) {
65         case PROP_EFFECT:
66                 if (priv->effect)
67                         g_object_unref (priv->effect);
68
69                 priv->effect = g_value_dup_object (value);
70                 break;
71         case PROP_ORIGIN_SURFACE:
72                 ev_transition_animation_set_origin_surface (EV_TRANSITION_ANIMATION (object),
73                                                             g_value_get_pointer (value));
74                 break;
75         case PROP_DEST_SURFACE:
76                 ev_transition_animation_set_dest_surface (EV_TRANSITION_ANIMATION (object),
77                                                           g_value_get_pointer (value));
78                 break;
79         default:
80                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
81         }
82 }
83
84 static void
85 ev_transition_animation_get_property (GObject      *object,
86                                       guint         prop_id,
87                                       GValue       *value,
88                                       GParamSpec   *pspec)
89 {
90         EvTransitionAnimationPriv *priv;
91
92         priv = EV_TRANSITION_ANIMATION_GET_PRIVATE (object);
93
94         switch (prop_id) {
95         case PROP_EFFECT:
96                 g_value_set_object (value, priv->effect);
97                 break;
98         case PROP_ORIGIN_SURFACE:
99                 g_value_set_pointer (value, priv->origin_surface);
100                 break;
101         case PROP_DEST_SURFACE:
102                 g_value_set_pointer (value, priv->dest_surface);
103                 break;
104         default:
105                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
106         }
107 }
108
109 static void
110 ev_transition_animation_finalize (GObject *object)
111 {
112         EvTransitionAnimationPriv *priv;
113
114         priv = EV_TRANSITION_ANIMATION_GET_PRIVATE (object);
115
116         if (priv->effect)
117                 g_object_unref (priv->effect);
118
119         if (priv->origin_surface)
120                 cairo_surface_destroy (priv->origin_surface);
121
122         if (priv->dest_surface)
123                 cairo_surface_destroy (priv->dest_surface);
124
125         G_OBJECT_CLASS (ev_transition_animation_parent_class)->finalize (object);
126 }
127
128 static GObject *
129 ev_transition_animation_constructor (GType                  type,
130                                      guint                  n_construct_properties,
131                                      GObjectConstructParam *construct_params)
132 {
133         GObject *object;
134         EvTransitionAnimationPriv *priv;
135         EvTransitionEffect *effect;
136         gint duration;
137
138         object = G_OBJECT_CLASS (ev_transition_animation_parent_class)->constructor (type,
139                                                                                      n_construct_properties,
140                                                                                      construct_params);
141
142         priv = EV_TRANSITION_ANIMATION_GET_PRIVATE (object);
143         effect = priv->effect;
144
145         g_object_get (effect, "duration", &duration, NULL);
146         ev_timeline_set_duration (EV_TIMELINE (object), duration * 1000);
147
148         return object;
149 }
150
151 static void
152 ev_transition_animation_class_init (EvTransitionAnimationClass *klass)
153 {
154         GObjectClass *object_class = G_OBJECT_CLASS (klass);
155
156         object_class->set_property = ev_transition_animation_set_property;
157         object_class->get_property = ev_transition_animation_get_property;
158         object_class->finalize = ev_transition_animation_finalize;
159         object_class->constructor = ev_transition_animation_constructor;
160
161         g_object_class_install_property (object_class,
162                                          PROP_EFFECT,
163                                          g_param_spec_object ("effect",
164                                                               "Effect",
165                                                               "Transition effect description",
166                                                               EV_TYPE_TRANSITION_EFFECT,
167                                                               G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
168         g_object_class_install_property (object_class,
169                                          PROP_ORIGIN_SURFACE,
170                                          g_param_spec_pointer ("origin-surface",
171                                                                "Origin surface",
172                                                                "Cairo surface from which the animation will happen",
173                                                                G_PARAM_READWRITE));
174         g_object_class_install_property (object_class,
175                                          PROP_DEST_SURFACE,
176                                          g_param_spec_pointer ("dest-surface",
177                                                                "Destination surface",
178                                                                "Cairo surface to which the animation will happen",
179                                                                G_PARAM_READWRITE));
180
181         g_type_class_add_private (klass, sizeof (EvTransitionAnimationPriv));
182 }
183
184 static void
185 paint_surface (cairo_t         *cr,
186                cairo_surface_t *surface,
187                gdouble          x_offset,
188                gdouble          y_offset,
189                gdouble          alpha,
190                GdkRectangle     page_area)
191 {
192         cairo_save (cr);
193
194         gdk_cairo_rectangle (cr, &page_area);
195         cairo_surface_set_device_offset (surface, x_offset, y_offset);
196         cairo_set_source_surface (cr, surface, 0, 0);
197
198         if (alpha == 0.)
199                 cairo_paint (cr);
200         else
201                 cairo_paint_with_alpha (cr, alpha);
202
203         cairo_restore (cr);
204 }
205
206 /* animations */
207 static void
208 ev_transition_animation_split (cairo_t               *cr,
209                                EvTransitionAnimation *animation,
210                                EvTransitionEffect    *effect,
211                                gdouble                progress,
212                                GdkRectangle           page_area)
213 {
214         EvTransitionAnimationPriv *priv;
215         EvTransitionEffectAlignment alignment;
216         EvTransitionEffectDirection direction;
217         gint width, height;
218
219         priv = EV_TRANSITION_ANIMATION_GET_PRIVATE (animation);
220         width = page_area.width;
221         height = page_area.height;
222
223         g_object_get (effect,
224                       "alignment", &alignment,
225                       "direction", &direction,
226                       NULL);
227
228         if (direction == EV_TRANSITION_DIRECTION_INWARD) {
229                 paint_surface (cr, priv->dest_surface, 0, 0, 0, page_area);
230
231                 if (alignment == EV_TRANSITION_ALIGNMENT_HORIZONTAL) {
232                         cairo_rectangle (cr,
233                                          0,
234                                          height * progress / 2,
235                                          width,
236                                          height * (1 - progress));
237                 } else {
238                         cairo_rectangle (cr,
239                                          width * progress / 2,
240                                          0,
241                                          width * (1 - progress),
242                                          height);
243                 }
244
245                 cairo_clip (cr);
246
247                 paint_surface (cr, priv->origin_surface, 0, 0, 0, page_area);
248         } else {
249                 paint_surface (cr, priv->origin_surface, 0, 0, 0, page_area);
250
251                 if (alignment == EV_TRANSITION_ALIGNMENT_HORIZONTAL) {
252                         cairo_rectangle (cr,
253                                          0,
254                                          (height / 2) - (height * progress / 2),
255                                          width,
256                                          height * progress);
257                 } else {
258                         cairo_rectangle (cr,
259                                          (width / 2) - (width * progress / 2),
260                                          0,
261                                          width * progress,
262                                          height);
263                 }
264
265                 cairo_clip (cr);
266
267                 paint_surface (cr, priv->dest_surface, 0, 0, 0, page_area);
268         }
269 }
270
271 static void
272 ev_transition_animation_blinds (cairo_t               *cr,
273                                 EvTransitionAnimation *animation,
274                                 EvTransitionEffect    *effect,
275                                 gdouble                progress,
276                                 GdkRectangle           page_area)
277 {
278         EvTransitionAnimationPriv *priv;
279         EvTransitionEffectAlignment alignment;
280         gint width, height, i;
281
282         priv = EV_TRANSITION_ANIMATION_GET_PRIVATE (animation);
283         width = page_area.width;
284         height = page_area.height;
285
286         g_object_get (effect,
287                       "alignment", &alignment,
288                       NULL);
289
290         paint_surface (cr, priv->origin_surface, 0, 0, 0, page_area);
291
292         for (i = 0; i < N_BLINDS; i++) {
293                 cairo_save (cr);
294
295                 if (alignment == EV_TRANSITION_ALIGNMENT_HORIZONTAL) {
296                         cairo_rectangle (cr,
297                                          0,
298                                          height / N_BLINDS * i,
299                                          width,
300                                          height / N_BLINDS * progress);
301                 } else {
302                         cairo_rectangle (cr,
303                                          width / N_BLINDS * i,
304                                          0,
305                                          width / N_BLINDS * progress,
306                                          height);
307                 }
308
309                 cairo_clip (cr);
310                 paint_surface (cr, priv->dest_surface, 0, 0, 0, page_area);
311                 cairo_restore (cr);
312         }
313 }
314
315 static void
316 ev_transition_animation_box (cairo_t               *cr,
317                              EvTransitionAnimation *animation,
318                              EvTransitionEffect    *effect,
319                              gdouble                progress,
320                              GdkRectangle           page_area)
321 {
322         EvTransitionAnimationPriv *priv;
323         EvTransitionEffectDirection direction;
324         gint width, height;
325
326         priv = EV_TRANSITION_ANIMATION_GET_PRIVATE (animation);
327         width = page_area.width;
328         height = page_area.height;
329
330         g_object_get (effect,
331                       "direction", &direction,
332                       NULL);
333
334         if (direction == EV_TRANSITION_DIRECTION_INWARD) {
335                 paint_surface (cr, priv->dest_surface, 0, 0, 0, page_area);
336
337                 cairo_rectangle (cr,
338                                  width * progress / 2,
339                                  height * progress / 2,
340                                  width * (1 - progress),
341                                  height * (1 - progress));
342                 cairo_clip (cr);
343
344                 paint_surface (cr, priv->origin_surface, 0, 0, 0, page_area);
345         } else {
346                 paint_surface (cr, priv->origin_surface, 0, 0, 0, page_area);
347
348                 cairo_rectangle (cr,
349                                  (width / 2) - (width * progress / 2),
350                                  (height / 2) - (height * progress / 2),
351                                  width * progress,
352                                  height * progress);
353                 cairo_clip (cr);
354
355                 paint_surface (cr, priv->dest_surface, 0, 0, 0, page_area);
356         }
357 }
358
359 static void
360 ev_transition_animation_wipe (cairo_t               *cr,
361                               EvTransitionAnimation *animation,
362                               EvTransitionEffect    *effect,
363                               gdouble                progress,
364                               GdkRectangle           page_area)
365 {
366         EvTransitionAnimationPriv *priv;
367         gint width, height;
368         gint angle;
369
370         priv = EV_TRANSITION_ANIMATION_GET_PRIVATE (animation);
371         width = page_area.width;
372         height = page_area.height;
373
374         g_object_get (effect,
375                       "angle", &angle,
376                       NULL);
377
378         paint_surface (cr, priv->origin_surface, 0, 0, 0, page_area);
379
380         if (angle == 0) {
381                 /* left to right */
382                 cairo_rectangle (cr,
383                                  0, 0,
384                                  width * progress,
385                                  height);
386         } else if (angle <= 90) {
387                 /* bottom to top */
388                 cairo_rectangle (cr,
389                                  0,
390                                  height * (1 - progress),
391                                  width,
392                                  height * progress);
393         } else if (angle <= 180) {
394                 /* right to left */
395                 cairo_rectangle (cr,
396                                  width * (1 - progress),
397                                  0,
398                                  width * progress,
399                                  height);
400         } else if (angle <= 270) {
401                 /* top to bottom */
402                 cairo_rectangle (cr,
403                                  0, 0,
404                                  width,
405                                  height * progress);
406         }
407
408         cairo_clip (cr);
409
410         paint_surface (cr, priv->dest_surface, 0, 0, 0, page_area);
411 }
412
413 static void
414 ev_transition_animation_dissolve (cairo_t               *cr,
415                                   EvTransitionAnimation *animation,
416                                   EvTransitionEffect    *effect,
417                                   gdouble                progress,
418                                   GdkRectangle           page_area)
419 {
420         EvTransitionAnimationPriv *priv;
421
422         priv = EV_TRANSITION_ANIMATION_GET_PRIVATE (animation);
423
424         paint_surface (cr, priv->dest_surface, 0, 0, 0, page_area);
425         paint_surface (cr, priv->origin_surface, 0, 0, 1 - progress, page_area);
426 }
427
428 static void
429 ev_transition_animation_push (cairo_t               *cr,
430                               EvTransitionAnimation *animation,
431                               EvTransitionEffect    *effect,
432                               gdouble                progress,
433                               GdkRectangle           page_area)
434 {
435         EvTransitionAnimationPriv *priv;
436         gint width, height;
437         gint angle;
438
439         priv = EV_TRANSITION_ANIMATION_GET_PRIVATE (animation);
440         width = page_area.width;
441         height = page_area.height;
442
443         g_object_get (effect,
444                       "angle", &angle,
445                       NULL);
446
447         if (angle == 0) {
448                 /* left to right */
449                 paint_surface (cr, priv->origin_surface, - (width * progress), 0, 0, page_area);
450                 paint_surface (cr, priv->dest_surface, width * (1 - progress), 0, 0, page_area);
451         } else {
452                 /* top to bottom */
453                 paint_surface (cr, priv->origin_surface, 0, - (height * progress), 0, page_area);
454                 paint_surface (cr, priv->dest_surface, 0, height * (1 - progress), 0, page_area);
455         }
456 }
457
458 static void
459 ev_transition_animation_cover (cairo_t               *cr,
460                                EvTransitionAnimation *animation,
461                                EvTransitionEffect    *effect,
462                                gdouble                progress,
463                                GdkRectangle           page_area)
464 {
465         EvTransitionAnimationPriv *priv;
466         gint width, height;
467         gint angle;
468
469         priv = EV_TRANSITION_ANIMATION_GET_PRIVATE (animation);
470         width = page_area.width;
471         height = page_area.height;
472
473         g_object_get (effect,
474                       "angle", &angle,
475                       NULL);
476
477         paint_surface (cr, priv->origin_surface, 0, 0, 0, page_area);
478
479         if (angle == 0) {
480                 /* left to right */
481                 paint_surface (cr, priv->dest_surface, width * (1 - progress), 0, 0, page_area);
482         } else {
483                 /* top to bottom */
484                 paint_surface (cr, priv->dest_surface, 0, height * (1 - progress), 0, page_area);
485         }
486 }
487
488 static void
489 ev_transition_animation_uncover (cairo_t               *cr,
490                                  EvTransitionAnimation *animation,
491                                  EvTransitionEffect    *effect,
492                                  gdouble                progress,
493                                  GdkRectangle           page_area)
494 {
495         EvTransitionAnimationPriv *priv;
496         gint width, height;
497         gint angle;
498
499         priv = EV_TRANSITION_ANIMATION_GET_PRIVATE (animation);
500         width = page_area.width;
501         height = page_area.height;
502
503         g_object_get (effect,
504                       "angle", &angle,
505                       NULL);
506
507         paint_surface (cr, priv->dest_surface, 0, 0, 0, page_area);
508
509         if (angle == 0) {
510                 /* left to right */
511                 paint_surface (cr, priv->origin_surface, - (width * progress), 0, 0, page_area);
512         } else {
513                 /* top to bottom */
514                 paint_surface (cr, priv->origin_surface, 0, - (height * progress), 0, page_area);
515         }
516 }
517
518 static void
519 ev_transition_animation_fade (cairo_t               *cr,
520                               EvTransitionAnimation *animation,
521                               EvTransitionEffect    *effect,
522                               gdouble                progress,
523                               GdkRectangle           page_area)
524 {
525         EvTransitionAnimationPriv *priv;
526
527         priv = EV_TRANSITION_ANIMATION_GET_PRIVATE (animation);
528
529         paint_surface (cr, priv->origin_surface, 0, 0, 0, page_area);
530         paint_surface (cr, priv->dest_surface, 0, 0, progress, page_area);
531 }
532
533 void
534 ev_transition_animation_paint (EvTransitionAnimation *animation,
535                                cairo_t               *cr,
536                                GdkRectangle           page_area)
537 {
538         EvTransitionAnimationPriv *priv;
539         EvTransitionEffectType type;
540         gdouble progress;
541
542         g_return_if_fail (EV_IS_TRANSITION_ANIMATION (animation));
543
544         priv = EV_TRANSITION_ANIMATION_GET_PRIVATE (animation);
545
546         if (!priv->dest_surface) {
547                 /* animation is still not ready, paint the origin surface */
548                 paint_surface (cr, priv->origin_surface, 0, 0, 0, page_area);
549                 return;
550         }
551
552         g_object_get (priv->effect, "type", &type, NULL);
553         progress = ev_timeline_get_progress (EV_TIMELINE (animation));
554
555         switch (type) {
556         case EV_TRANSITION_EFFECT_REPLACE:
557                 /* just paint the destination slide */
558                 paint_surface (cr, priv->dest_surface, 0, 0, 0, page_area);
559                 break;
560         case EV_TRANSITION_EFFECT_SPLIT:
561                 ev_transition_animation_split (cr, animation, priv->effect, progress, page_area);
562                 break;
563         case EV_TRANSITION_EFFECT_BLINDS:
564                 ev_transition_animation_blinds (cr, animation, priv->effect, progress, page_area);
565                 break;
566         case EV_TRANSITION_EFFECT_BOX:
567                 ev_transition_animation_box (cr, animation, priv->effect, progress, page_area);
568                 break;
569         case EV_TRANSITION_EFFECT_WIPE:
570                 ev_transition_animation_wipe (cr, animation, priv->effect, progress, page_area);
571                 break;
572         case EV_TRANSITION_EFFECT_DISSOLVE:
573                 ev_transition_animation_dissolve (cr, animation, priv->effect, progress, page_area);
574                 break;
575         case EV_TRANSITION_EFFECT_PUSH:
576                 ev_transition_animation_push (cr, animation, priv->effect, progress, page_area);
577                 break;
578         case EV_TRANSITION_EFFECT_COVER:
579                 ev_transition_animation_cover (cr, animation, priv->effect, progress, page_area);
580                 break;
581         case EV_TRANSITION_EFFECT_UNCOVER:
582                 ev_transition_animation_uncover (cr, animation, priv->effect, progress, page_area);
583                 break;
584         case EV_TRANSITION_EFFECT_FADE:
585                 ev_transition_animation_fade (cr, animation, priv->effect, progress, page_area);
586                 break;
587         default: {
588                 GEnumValue *enum_value;
589
590                 enum_value = g_enum_get_value (g_type_class_peek (EV_TYPE_TRANSITION_EFFECT_TYPE), type);
591
592                 g_warning ("Unimplemented transition animation: '%s', "
593                            "please post a bug report in Evince bugzilla "
594                            "(http://bugzilla.gnome.org) with a testcase.",
595                            enum_value->value_nick);
596
597                 /* just paint the destination slide */
598                 paint_surface (cr, priv->dest_surface, 0, 0, 0, page_area);
599                 }
600         }
601 }
602
603 EvTransitionAnimation *
604 ev_transition_animation_new (EvTransitionEffect *effect)
605 {
606         g_return_val_if_fail (EV_IS_TRANSITION_EFFECT (effect), NULL);
607
608         return g_object_new (EV_TYPE_TRANSITION_ANIMATION,
609                              "effect", effect,
610                              NULL);
611 }
612
613 void
614 ev_transition_animation_set_origin_surface (EvTransitionAnimation *animation,
615                                             cairo_surface_t       *origin_surface)
616 {
617         EvTransitionAnimationPriv *priv;
618         cairo_surface_t *surface;
619
620         g_return_if_fail (EV_IS_TRANSITION_ANIMATION (animation));
621
622         priv = EV_TRANSITION_ANIMATION_GET_PRIVATE (animation);
623
624         if (priv->origin_surface == origin_surface)
625                 return;
626
627         surface = cairo_surface_reference (origin_surface);
628
629         if (priv->origin_surface)
630                 cairo_surface_destroy (priv->origin_surface);
631
632         priv->origin_surface = surface;
633         g_object_notify (G_OBJECT (animation), "origin-surface");
634
635         if (priv->origin_surface && priv->dest_surface)
636                 ev_timeline_start (EV_TIMELINE (animation));
637 }
638
639 void
640 ev_transition_animation_set_dest_surface (EvTransitionAnimation *animation,
641                                           cairo_surface_t       *dest_surface)
642 {
643         EvTransitionAnimationPriv *priv;
644         cairo_surface_t *surface;
645
646         g_return_if_fail (EV_IS_TRANSITION_ANIMATION (animation));
647
648         priv = EV_TRANSITION_ANIMATION_GET_PRIVATE (animation);
649
650         if (priv->dest_surface == dest_surface)
651                 return;
652
653         surface = cairo_surface_reference (dest_surface);
654
655         if (priv->dest_surface)
656                 cairo_surface_destroy (priv->dest_surface);
657
658         priv->dest_surface = surface;
659         g_object_notify (G_OBJECT (animation), "dest-surface");
660
661         if (priv->origin_surface && priv->dest_surface)
662                 ev_timeline_start (EV_TIMELINE (animation));
663 }
664
665 gboolean
666 ev_transition_animation_ready (EvTransitionAnimation *animation)
667 {
668         EvTransitionAnimationPriv *priv;
669
670         g_return_val_if_fail (EV_IS_TRANSITION_ANIMATION (animation), FALSE);
671
672         priv = EV_TRANSITION_ANIMATION_GET_PRIVATE (animation);
673
674         return (priv->origin_surface != NULL);
675 }