]> www.fi.muni.cz Git - evince.git/blob - libview/ev-transition-animation.c
[dualscreen] fix crash on ctrl+w and fix control window closing
[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., 51 Franklin Street, Fifth Floor, 
19  * Boston, MA 02110-1301, 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_clip (cr);
196         cairo_surface_set_device_offset (surface, x_offset, y_offset);
197         cairo_set_source_surface (cr, surface, 0, 0);
198
199         if (alpha == 1.)
200                 cairo_paint (cr);
201         else
202                 cairo_paint_with_alpha (cr, alpha);
203
204         cairo_restore (cr);
205 }
206
207 /* animations */
208 static void
209 ev_transition_animation_split (cairo_t               *cr,
210                                EvTransitionAnimation *animation,
211                                EvTransitionEffect    *effect,
212                                gdouble                progress,
213                                GdkRectangle           page_area)
214 {
215         EvTransitionAnimationPriv *priv;
216         EvTransitionEffectAlignment alignment;
217         EvTransitionEffectDirection direction;
218         gint width, height;
219
220         priv = EV_TRANSITION_ANIMATION_GET_PRIVATE (animation);
221         width = page_area.width;
222         height = page_area.height;
223
224         g_object_get (effect,
225                       "alignment", &alignment,
226                       "direction", &direction,
227                       NULL);
228
229         if (direction == EV_TRANSITION_DIRECTION_INWARD) {
230                 paint_surface (cr, priv->dest_surface, 0, 0, 1., page_area);
231
232                 if (alignment == EV_TRANSITION_ALIGNMENT_HORIZONTAL) {
233                         cairo_rectangle (cr,
234                                          0,
235                                          height * progress / 2,
236                                          width,
237                                          height * (1 - progress));
238                 } else {
239                         cairo_rectangle (cr,
240                                          width * progress / 2,
241                                          0,
242                                          width * (1 - progress),
243                                          height);
244                 }
245
246                 cairo_clip (cr);
247
248                 paint_surface (cr, priv->origin_surface, 0, 0, 1., page_area);
249         } else {
250                 paint_surface (cr, priv->origin_surface, 0, 0, 1., page_area);
251
252                 if (alignment == EV_TRANSITION_ALIGNMENT_HORIZONTAL) {
253                         cairo_rectangle (cr,
254                                          0,
255                                          (height / 2) - (height * progress / 2),
256                                          width,
257                                          height * progress);
258                 } else {
259                         cairo_rectangle (cr,
260                                          (width / 2) - (width * progress / 2),
261                                          0,
262                                          width * progress,
263                                          height);
264                 }
265
266                 cairo_clip (cr);
267
268                 paint_surface (cr, priv->dest_surface, 0, 0, 1., page_area);
269         }
270 }
271
272 static void
273 ev_transition_animation_blinds (cairo_t               *cr,
274                                 EvTransitionAnimation *animation,
275                                 EvTransitionEffect    *effect,
276                                 gdouble                progress,
277                                 GdkRectangle           page_area)
278 {
279         EvTransitionAnimationPriv *priv;
280         EvTransitionEffectAlignment alignment;
281         gint width, height, i;
282
283         priv = EV_TRANSITION_ANIMATION_GET_PRIVATE (animation);
284         width = page_area.width;
285         height = page_area.height;
286
287         g_object_get (effect,
288                       "alignment", &alignment,
289                       NULL);
290
291         paint_surface (cr, priv->origin_surface, 0, 0, 1., page_area);
292
293         for (i = 0; i < N_BLINDS; i++) {
294                 cairo_save (cr);
295
296                 if (alignment == EV_TRANSITION_ALIGNMENT_HORIZONTAL) {
297                         cairo_rectangle (cr,
298                                          0,
299                                          height / N_BLINDS * i,
300                                          width,
301                                          height / N_BLINDS * progress);
302                 } else {
303                         cairo_rectangle (cr,
304                                          width / N_BLINDS * i,
305                                          0,
306                                          width / N_BLINDS * progress,
307                                          height);
308                 }
309
310                 cairo_clip (cr);
311                 paint_surface (cr, priv->dest_surface, 0, 0, 1., page_area);
312                 cairo_restore (cr);
313         }
314 }
315
316 static void
317 ev_transition_animation_box (cairo_t               *cr,
318                              EvTransitionAnimation *animation,
319                              EvTransitionEffect    *effect,
320                              gdouble                progress,
321                              GdkRectangle           page_area)
322 {
323         EvTransitionAnimationPriv *priv;
324         EvTransitionEffectDirection direction;
325         gint width, height;
326
327         priv = EV_TRANSITION_ANIMATION_GET_PRIVATE (animation);
328         width = page_area.width;
329         height = page_area.height;
330
331         g_object_get (effect,
332                       "direction", &direction,
333                       NULL);
334
335         if (direction == EV_TRANSITION_DIRECTION_INWARD) {
336                 paint_surface (cr, priv->dest_surface, 0, 0, 1., page_area);
337
338                 cairo_rectangle (cr,
339                                  width * progress / 2,
340                                  height * progress / 2,
341                                  width * (1 - progress),
342                                  height * (1 - progress));
343                 cairo_clip (cr);
344
345                 paint_surface (cr, priv->origin_surface, 0, 0, 1., page_area);
346         } else {
347                 paint_surface (cr, priv->origin_surface, 0, 0, 1., page_area);
348
349                 cairo_rectangle (cr,
350                                  (width / 2) - (width * progress / 2),
351                                  (height / 2) - (height * progress / 2),
352                                  width * progress,
353                                  height * progress);
354                 cairo_clip (cr);
355
356                 paint_surface (cr, priv->dest_surface, 0, 0, 1., page_area);
357         }
358 }
359
360 static void
361 ev_transition_animation_wipe (cairo_t               *cr,
362                               EvTransitionAnimation *animation,
363                               EvTransitionEffect    *effect,
364                               gdouble                progress,
365                               GdkRectangle           page_area)
366 {
367         EvTransitionAnimationPriv *priv;
368         gint width, height;
369         gint angle;
370
371         priv = EV_TRANSITION_ANIMATION_GET_PRIVATE (animation);
372         width = page_area.width;
373         height = page_area.height;
374
375         g_object_get (effect,
376                       "angle", &angle,
377                       NULL);
378
379         paint_surface (cr, priv->origin_surface, 0, 0, 1., page_area);
380
381         if (angle == 0) {
382                 /* left to right */
383                 cairo_rectangle (cr,
384                                  0, 0,
385                                  width * progress,
386                                  height);
387         } else if (angle <= 90) {
388                 /* bottom to top */
389                 cairo_rectangle (cr,
390                                  0,
391                                  height * (1 - progress),
392                                  width,
393                                  height * progress);
394         } else if (angle <= 180) {
395                 /* right to left */
396                 cairo_rectangle (cr,
397                                  width * (1 - progress),
398                                  0,
399                                  width * progress,
400                                  height);
401         } else if (angle <= 270) {
402                 /* top to bottom */
403                 cairo_rectangle (cr,
404                                  0, 0,
405                                  width,
406                                  height * progress);
407         }
408
409         cairo_clip (cr);
410
411         paint_surface (cr, priv->dest_surface, 0, 0, 1., page_area);
412 }
413
414 static void
415 ev_transition_animation_dissolve (cairo_t               *cr,
416                                   EvTransitionAnimation *animation,
417                                   EvTransitionEffect    *effect,
418                                   gdouble                progress,
419                                   GdkRectangle           page_area)
420 {
421         EvTransitionAnimationPriv *priv;
422
423         priv = EV_TRANSITION_ANIMATION_GET_PRIVATE (animation);
424
425         paint_surface (cr, priv->dest_surface, 0, 0, 1., page_area);
426         paint_surface (cr, priv->origin_surface, 0, 0, 1 - progress, page_area);
427 }
428
429 static void
430 ev_transition_animation_push (cairo_t               *cr,
431                               EvTransitionAnimation *animation,
432                               EvTransitionEffect    *effect,
433                               gdouble                progress,
434                               GdkRectangle           page_area)
435 {
436         EvTransitionAnimationPriv *priv;
437         gint width, height;
438         gint angle;
439
440         priv = EV_TRANSITION_ANIMATION_GET_PRIVATE (animation);
441         width = page_area.width;
442         height = page_area.height;
443
444         g_object_get (effect,
445                       "angle", &angle,
446                       NULL);
447
448         if (angle == 0) {
449                 /* left to right */
450                 paint_surface (cr, priv->origin_surface, - (width * progress), 0, 1., page_area);
451                 paint_surface (cr, priv->dest_surface, width * (1 - progress), 0, 1., page_area);
452         } else {
453                 /* top to bottom */
454                 paint_surface (cr, priv->origin_surface, 0, - (height * progress), 1., page_area);
455                 paint_surface (cr, priv->dest_surface, 0, height * (1 - progress), 1., page_area);
456         }
457 }
458
459 static void
460 ev_transition_animation_cover (cairo_t               *cr,
461                                EvTransitionAnimation *animation,
462                                EvTransitionEffect    *effect,
463                                gdouble                progress,
464                                GdkRectangle           page_area)
465 {
466         EvTransitionAnimationPriv *priv;
467         gint width, height;
468         gint angle;
469
470         priv = EV_TRANSITION_ANIMATION_GET_PRIVATE (animation);
471         width = page_area.width;
472         height = page_area.height;
473
474         g_object_get (effect,
475                       "angle", &angle,
476                       NULL);
477
478         paint_surface (cr, priv->origin_surface, 0, 0, 1., page_area);
479
480         if (angle == 0) {
481                 /* left to right */
482                 paint_surface (cr, priv->dest_surface, width * (1 - progress), 0, 1., page_area);
483         } else {
484                 /* top to bottom */
485                 paint_surface (cr, priv->dest_surface, 0, height * (1 - progress), 1., page_area);
486         }
487 }
488
489 static void
490 ev_transition_animation_uncover (cairo_t               *cr,
491                                  EvTransitionAnimation *animation,
492                                  EvTransitionEffect    *effect,
493                                  gdouble                progress,
494                                  GdkRectangle           page_area)
495 {
496         EvTransitionAnimationPriv *priv;
497         gint width, height;
498         gint angle;
499
500         priv = EV_TRANSITION_ANIMATION_GET_PRIVATE (animation);
501         width = page_area.width;
502         height = page_area.height;
503
504         g_object_get (effect,
505                       "angle", &angle,
506                       NULL);
507
508         paint_surface (cr, priv->dest_surface, 0, 0, 1., page_area);
509
510         if (angle == 0) {
511                 /* left to right */
512                 paint_surface (cr, priv->origin_surface, - (width * progress), 0, 1., page_area);
513         } else {
514                 /* top to bottom */
515                 paint_surface (cr, priv->origin_surface, 0, - (height * progress), 1., page_area);
516         }
517 }
518
519 static void
520 ev_transition_animation_fade (cairo_t               *cr,
521                               EvTransitionAnimation *animation,
522                               EvTransitionEffect    *effect,
523                               gdouble                progress,
524                               GdkRectangle           page_area)
525 {
526         EvTransitionAnimationPriv *priv;
527
528         priv = EV_TRANSITION_ANIMATION_GET_PRIVATE (animation);
529
530         paint_surface (cr, priv->origin_surface, 0, 0, 1., page_area);
531         paint_surface (cr, priv->dest_surface, 0, 0, progress, page_area);
532 }
533
534 void
535 ev_transition_animation_paint (EvTransitionAnimation *animation,
536                                cairo_t               *cr,
537                                GdkRectangle           page_area)
538 {
539         EvTransitionAnimationPriv *priv;
540         EvTransitionEffectType type;
541         gdouble progress;
542
543         g_return_if_fail (EV_IS_TRANSITION_ANIMATION (animation));
544
545         priv = EV_TRANSITION_ANIMATION_GET_PRIVATE (animation);
546
547         if (!priv->dest_surface) {
548                 /* animation is still not ready, paint the origin surface */
549                 paint_surface (cr, priv->origin_surface, 0, 0, 1., page_area);
550                 return;
551         }
552
553         g_object_get (priv->effect, "type", &type, NULL);
554         progress = ev_timeline_get_progress (EV_TIMELINE (animation));
555
556         switch (type) {
557         case EV_TRANSITION_EFFECT_REPLACE:
558                 /* just paint the destination slide */
559                 paint_surface (cr, priv->dest_surface, 0, 0, 1., page_area);
560                 break;
561         case EV_TRANSITION_EFFECT_SPLIT:
562                 ev_transition_animation_split (cr, animation, priv->effect, progress, page_area);
563                 break;
564         case EV_TRANSITION_EFFECT_BLINDS:
565                 ev_transition_animation_blinds (cr, animation, priv->effect, progress, page_area);
566                 break;
567         case EV_TRANSITION_EFFECT_BOX:
568                 ev_transition_animation_box (cr, animation, priv->effect, progress, page_area);
569                 break;
570         case EV_TRANSITION_EFFECT_WIPE:
571                 ev_transition_animation_wipe (cr, animation, priv->effect, progress, page_area);
572                 break;
573         case EV_TRANSITION_EFFECT_DISSOLVE:
574                 ev_transition_animation_dissolve (cr, animation, priv->effect, progress, page_area);
575                 break;
576         case EV_TRANSITION_EFFECT_PUSH:
577                 ev_transition_animation_push (cr, animation, priv->effect, progress, page_area);
578                 break;
579         case EV_TRANSITION_EFFECT_COVER:
580                 ev_transition_animation_cover (cr, animation, priv->effect, progress, page_area);
581                 break;
582         case EV_TRANSITION_EFFECT_UNCOVER:
583                 ev_transition_animation_uncover (cr, animation, priv->effect, progress, page_area);
584                 break;
585         case EV_TRANSITION_EFFECT_FADE:
586                 ev_transition_animation_fade (cr, animation, priv->effect, progress, page_area);
587                 break;
588         default: {
589                 GEnumValue *enum_value;
590
591                 enum_value = g_enum_get_value (g_type_class_peek (EV_TYPE_TRANSITION_EFFECT_TYPE), type);
592
593                 g_warning ("Unimplemented transition animation: '%s', "
594                            "please post a bug report in Evince bugzilla "
595                            "(http://bugzilla.gnome.org) with a testcase.",
596                            enum_value->value_nick);
597
598                 /* just paint the destination slide */
599                 paint_surface (cr, priv->dest_surface, 0, 0, 1., page_area);
600                 }
601         }
602 }
603
604 EvTransitionAnimation *
605 ev_transition_animation_new (EvTransitionEffect *effect)
606 {
607         g_return_val_if_fail (EV_IS_TRANSITION_EFFECT (effect), NULL);
608
609         return g_object_new (EV_TYPE_TRANSITION_ANIMATION,
610                              "effect", effect,
611                              NULL);
612 }
613
614 void
615 ev_transition_animation_set_origin_surface (EvTransitionAnimation *animation,
616                                             cairo_surface_t       *origin_surface)
617 {
618         EvTransitionAnimationPriv *priv;
619         cairo_surface_t *surface;
620
621         g_return_if_fail (EV_IS_TRANSITION_ANIMATION (animation));
622
623         priv = EV_TRANSITION_ANIMATION_GET_PRIVATE (animation);
624
625         if (priv->origin_surface == origin_surface)
626                 return;
627
628         surface = cairo_surface_reference (origin_surface);
629
630         if (priv->origin_surface)
631                 cairo_surface_destroy (priv->origin_surface);
632
633         priv->origin_surface = surface;
634         g_object_notify (G_OBJECT (animation), "origin-surface");
635
636         if (priv->origin_surface && priv->dest_surface)
637                 ev_timeline_start (EV_TIMELINE (animation));
638 }
639
640 void
641 ev_transition_animation_set_dest_surface (EvTransitionAnimation *animation,
642                                           cairo_surface_t       *dest_surface)
643 {
644         EvTransitionAnimationPriv *priv;
645         cairo_surface_t *surface;
646
647         g_return_if_fail (EV_IS_TRANSITION_ANIMATION (animation));
648
649         priv = EV_TRANSITION_ANIMATION_GET_PRIVATE (animation);
650
651         if (priv->dest_surface == dest_surface)
652                 return;
653
654         surface = cairo_surface_reference (dest_surface);
655
656         if (priv->dest_surface)
657                 cairo_surface_destroy (priv->dest_surface);
658
659         priv->dest_surface = surface;
660         g_object_notify (G_OBJECT (animation), "dest-surface");
661
662         if (priv->origin_surface && priv->dest_surface)
663                 ev_timeline_start (EV_TIMELINE (animation));
664 }
665
666 gboolean
667 ev_transition_animation_ready (EvTransitionAnimation *animation)
668 {
669         EvTransitionAnimationPriv *priv;
670
671         g_return_val_if_fail (EV_IS_TRANSITION_ANIMATION (animation), FALSE);
672
673         priv = EV_TRANSITION_ANIMATION_GET_PRIVATE (animation);
674
675         return (priv->origin_surface != NULL);
676 }