No issues found
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2012 Jonathan Matthew <jonathan@d14n.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * The Rhythmbox authors hereby grant permission for non-GPL compatible
11 * GStreamer plugins to be used and distributed together with GStreamer
12 * and Rhythmbox. This permission is above and beyond the permissions granted
13 * by the GPL license by which Rhythmbox is covered. If you modify this code
14 * you may extend this exception to your version of the code, but you are not
15 * obligated to do so. If you do not wish to do so, delete this exception
16 * statement from your version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
26 *
27 */
28
29 #include <config.h>
30
31 #include <glib/gi18n.h>
32
33 #include <widgets/rb-fading-image.h>
34 #include <lib/rb-debug.h>
35 #include <lib/rb-util.h>
36
37 #define RENDER_FRAME_TIME (1000 / 25) /* fps? */
38 #define BORDER_WIDTH 1.0
39
40 #define MAX_TOOLTIP_SIZE 256
41
42 static void rb_fading_image_class_init (RBFadingImageClass *klass);
43 static void rb_fading_image_init (RBFadingImage *image);
44
45 struct _RBFadingImagePrivate
46 {
47 char *fallback_icon;
48 cairo_pattern_t *current_pat;
49 cairo_pattern_t *next_pat;
50 cairo_pattern_t *fallback_pat;
51 gdouble alpha;
52
53 GdkPixbuf *current;
54 int current_width;
55 int current_height;
56 GdkPixbuf *current_full;
57 GdkPixbuf *next;
58 GdkPixbuf *next_full;
59 GdkPixbuf *fallback;
60 GdkPixbufLoader *loader;
61 gboolean next_set;
62
63 guint64 start;
64 guint64 end;
65 gulong render_timer_id;
66 };
67
68 G_DEFINE_TYPE (RBFadingImage, rb_fading_image, GTK_TYPE_WIDGET)
69
70 /**
71 * SECTION:rb-fading-image
72 * @short_description: image display widget that fades between two images
73 *
74 * This widget displays images, performing a simple fade transition between
75 * them. It also emits signals when URIs or pixbufs are dropped onto it.
76 */
77
78 enum
79 {
80 PROP_0,
81 PROP_FALLBACK
82 };
83
84 enum
85 {
86 URI_DROPPED,
87 PIXBUF_DROPPED,
88 LAST_SIGNAL
89 };
90
91 static guint signals[LAST_SIGNAL] = {0};
92
93 static gboolean
94 prepare_image (cairo_t *cr, cairo_pattern_t **save, GdkPixbuf *pixbuf)
95 {
96 if (*save != NULL) {
97 cairo_set_source (cr, *save);
98 return TRUE;
99 }
100
101 if (pixbuf != NULL) {
102 gdk_cairo_set_source_pixbuf (cr, pixbuf, 0.0, 0.0);
103 *save = cairo_get_source (cr);
104 cairo_pattern_reference (*save);
105 return TRUE;
106 } else {
107 return FALSE;
108 }
109 }
110
111 static void
112 impl_realize (GtkWidget *widget)
113 {
114 GtkAllocation allocation;
115 GdkWindowAttr attributes;
116 GdkWindow *window;
117 int attributes_mask;
118
119 gtk_widget_set_realized (widget, TRUE);
120
121 gtk_widget_get_allocation (widget, &allocation);
122
123 attributes.x = allocation.x;
124 attributes.y = allocation.y;
125 attributes.width = allocation.width;
126 attributes.height = allocation.height;
127 attributes.wclass = GDK_INPUT_OUTPUT;
128 attributes.window_type = GDK_WINDOW_CHILD;
129 attributes.event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_FOCUS_CHANGE_MASK;
130 attributes.visual = gtk_widget_get_visual (widget);
131
132 attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
133
134 window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask);
135 gtk_widget_set_window (widget, window);
136 gdk_window_set_user_data (window, widget);
137
138 gtk_widget_set_can_focus (widget, TRUE);
139 }
140
141 static void
142 draw_image (cairo_t *cr, int image_width, int image_height, int width, int height, cairo_extend_t extend, double alpha, gboolean border)
143 {
144 cairo_matrix_t matrix;
145 cairo_save (cr);
146
147 if (border) {
148 cairo_matrix_init_translate (&matrix,
149 - (BORDER_WIDTH + (width/2 - image_width/2)),
150 - (BORDER_WIDTH + (height/2 - image_height/2)));
151 } else {
152 cairo_matrix_init_translate (&matrix,
153 - (width/2 - image_width/2),
154 - (height/2 - image_height/2));
155 }
156 cairo_pattern_set_matrix (cairo_get_source (cr), &matrix);
157 cairo_pattern_set_filter (cairo_get_source (cr), CAIRO_FILTER_BEST);
158 cairo_pattern_set_extend (cairo_get_source (cr), extend);
159 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
160
161 cairo_rectangle (cr, BORDER_WIDTH, BORDER_WIDTH, width, height);
162 cairo_clip (cr);
163 cairo_paint_with_alpha (cr, alpha);
164
165 cairo_restore (cr);
166 }
167
168 static void
169 render_current (RBFadingImage *image, cairo_t *cr, int width, int height, gboolean border)
170 {
171 if (prepare_image (cr, &image->priv->current_pat, image->priv->current)) {
172 draw_image (cr,
173 image->priv->current_width,
174 image->priv->current_height,
175 width,
176 height,
177 CAIRO_EXTEND_NONE,
178 1.0 - image->priv->alpha,
179 border);
180 } else if (prepare_image (cr, &image->priv->fallback_pat, image->priv->fallback)) {
181 draw_image (cr,
182 gdk_pixbuf_get_width (image->priv->fallback),
183 gdk_pixbuf_get_height (image->priv->fallback),
184 width,
185 height,
186 CAIRO_EXTEND_PAD,
187 1.0 - image->priv->alpha,
188 border);
189 } else {
190 cairo_save (cr);
191 cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
192 cairo_rectangle (cr,
193 border ? BORDER_WIDTH : 0,
194 border ? BORDER_WIDTH : 0,
195 width,
196 height);
197 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
198 cairo_clip (cr);
199 cairo_paint (cr);
200 cairo_restore (cr);
201 }
202 }
203
204 static void
205 render_next (RBFadingImage *image, cairo_t *cr, int width, int height, gboolean border)
206 {
207 if (image->priv->alpha < 0.001) {
208 /* do nothing */
209 } else if (prepare_image (cr, &image->priv->next_pat, image->priv->next)) {
210 draw_image (cr,
211 gdk_pixbuf_get_width (image->priv->next),
212 gdk_pixbuf_get_height (image->priv->next),
213 width,
214 height,
215 CAIRO_EXTEND_NONE,
216 image->priv->alpha,
217 border);
218 } else if (prepare_image (cr, &image->priv->fallback_pat, image->priv->fallback)) {
219 draw_image (cr,
220 gdk_pixbuf_get_width (image->priv->fallback),
221 gdk_pixbuf_get_height (image->priv->fallback),
222 width,
223 height,
224 CAIRO_EXTEND_PAD,
225 image->priv->alpha,
226 border);
227 } else {
228 /* also do nothing */
229 }
230 }
231
232 static gboolean
233 impl_draw (GtkWidget *widget, cairo_t *cr)
234 {
235 RBFadingImage *image;
236 int border_width;
237 int border_height;
238 int width;
239 int height;
240
241 width = gtk_widget_get_allocated_width (widget);
242 height = gtk_widget_get_allocated_height (widget);
243 border_width = width;
244 border_height = height;
245
246 image = RB_FADING_IMAGE (widget);
247 if (image->priv->alpha > 0.01) {
248 if (image->priv->next) {
249 border_width = gdk_pixbuf_get_width (image->priv->next) + 2 * BORDER_WIDTH;
250 border_height = gdk_pixbuf_get_height (image->priv->next) + 2 * BORDER_WIDTH;
251 }
252 } else if (image->priv->current) {
253 border_width = gdk_pixbuf_get_width (image->priv->current) + 2 * BORDER_WIDTH;
254 border_height = gdk_pixbuf_get_height (image->priv->current) + 2 * BORDER_WIDTH;
255 }
256
257 cairo_save (cr);
258 cairo_set_line_width (cr, BORDER_WIDTH);
259 cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 1.0);
260 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
261 cairo_rectangle (cr,
262 (width - border_width) / 2,
263 (height - border_height) / 2,
264 border_width,
265 border_height);
266 cairo_stroke (cr);
267 cairo_restore (cr);
268
269 width -= 2 * BORDER_WIDTH;
270 height -= 2 * BORDER_WIDTH;
271
272 render_current (image, cr, width, height, TRUE);
273 render_next (image, cr, width, height, TRUE);
274
275 return TRUE;
276 }
277
278 static gboolean
279 impl_query_tooltip (GtkWidget *widget, int x, int y, gboolean keyboard_mode, GtkTooltip *tooltip)
280 {
281 RBFadingImage *image = RB_FADING_IMAGE (widget);
282 GdkPixbuf *scaled;
283 GdkPixbuf *full;
284
285 if (image->priv->render_timer_id != 0) {
286 full = image->priv->next_full;
287 scaled = image->priv->next;
288 } else {
289 full = image->priv->current_full;
290 scaled = image->priv->current;
291 }
292
293 if (full == NULL) {
294 gtk_tooltip_set_icon (tooltip, NULL);
295 gtk_tooltip_set_text (tooltip, _("Drop artwork here"));
296 return TRUE;
297 } else if (full == scaled) {
298 return FALSE;
299 } else {
300 gtk_tooltip_set_icon (tooltip, full);
301 return TRUE;
302 }
303 }
304
305 static void
306 impl_drag_data_received (GtkWidget *widget,
307 GdkDragContext *context,
308 int x,
309 int y,
310 GtkSelectionData *selection,
311 guint info,
312 guint time_)
313 {
314 GdkPixbuf *pixbuf;
315 char **uris;
316
317 pixbuf = gtk_selection_data_get_pixbuf (selection);
318 if (pixbuf != NULL) {
319 g_signal_emit (widget, signals[PIXBUF_DROPPED], 0, pixbuf);
320 g_object_unref (pixbuf);
321 return;
322 }
323
324 uris = gtk_selection_data_get_uris (selection);
325 if (uris != NULL) {
326 if (uris[0] != NULL) {
327 g_signal_emit (widget, signals[URI_DROPPED], 0, uris[0]);
328 }
329
330 g_strfreev (uris);
331 return;
332 }
333
334 rb_debug ("weird drag data received");
335 }
336
337 static void
338 impl_drag_data_get (GtkWidget *widget, GdkDragContext *context, GtkSelectionData *selection, guint info, guint time_)
339 {
340 RBFadingImage *image = RB_FADING_IMAGE (widget);
341
342 if (image->priv->current_full) {
343 gtk_selection_data_set_pixbuf (selection, image->priv->current_full);
344 }
345
346 /* might be nice if we could provide a uri here? */
347 }
348
349
350 static void
351 impl_finalize (GObject *object)
352 {
353 RBFadingImage *image = RB_FADING_IMAGE (object);
354
355 g_free (image->priv->fallback_icon);
356
357 if (image->priv->current_pat != NULL) {
358 cairo_pattern_destroy (image->priv->current_pat);
359 }
360 if (image->priv->next_pat != NULL) {
361 cairo_pattern_destroy (image->priv->next_pat);
362 }
363 if (image->priv->fallback_pat != NULL) {
364 cairo_pattern_destroy (image->priv->fallback_pat);
365 }
366
367 G_OBJECT_CLASS (rb_fading_image_parent_class)->finalize (object);
368 }
369
370 static void
371 impl_dispose (GObject *object)
372 {
373 RBFadingImage *image = RB_FADING_IMAGE (object);
374
375 if (image->priv->render_timer_id != 0) {
376 g_source_remove (image->priv->render_timer_id);
377 image->priv->render_timer_id = 0;
378 }
379 if (image->priv->current != NULL) {
380 g_object_unref (image->priv->current);
381 image->priv->current = NULL;
382 }
383 if (image->priv->next != NULL) {
384 g_object_unref (image->priv->next);
385 image->priv->next = NULL;
386 }
387 if (image->priv->fallback != NULL) {
388 g_object_unref (image->priv->fallback);
389 image->priv->fallback = NULL;
390 }
391
392 G_OBJECT_CLASS (rb_fading_image_parent_class)->dispose (object);
393 }
394
395 static void
396 impl_constructed (GObject *object)
397 {
398 RBFadingImage *image;
399
400 RB_CHAIN_GOBJECT_METHOD (rb_fading_image_parent_class, constructed, object);
401
402 image = RB_FADING_IMAGE (object);
403
404 gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (image)),
405 GTK_STYLE_CLASS_SPINNER);
406
407 if (image->priv->fallback_icon != NULL) {
408 GError *error = NULL;
409 image->priv->fallback =
410 gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
411 image->priv->fallback_icon,
412 48,
413 GTK_ICON_LOOKUP_FORCE_SIZE,
414 &error);
415 if (error != NULL) {
416 g_warning ("couldn't load fallback icon %s: %s", image->priv->fallback_icon, error->message);
417 g_clear_error (&error);
418 }
419 }
420
421 gtk_widget_set_has_tooltip (GTK_WIDGET (image), TRUE);
422
423 /* drag and drop target */
424 gtk_drag_dest_set (GTK_WIDGET (image), GTK_DEST_DEFAULT_ALL, NULL, 0, GDK_ACTION_COPY);
425 gtk_drag_dest_add_image_targets (GTK_WIDGET (image));
426 gtk_drag_dest_add_uri_targets (GTK_WIDGET (image));
427
428 /* drag and drop source */
429 gtk_drag_source_set (GTK_WIDGET (image), GDK_BUTTON1_MASK, NULL, 0, GDK_ACTION_COPY);
430 gtk_drag_source_add_image_targets (GTK_WIDGET (image));
431 }
432
433
434 static void
435 impl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
436 {
437 RBFadingImage *image = RB_FADING_IMAGE (object);
438
439 switch (prop_id) {
440 case PROP_FALLBACK:
441 g_value_set_string (value, image->priv->fallback_icon);
442 break;
443 default:
444 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
445 break;
446 }
447 }
448
449 static void
450 impl_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
451 {
452 RBFadingImage *image = RB_FADING_IMAGE (object);
453
454 switch (prop_id) {
455 case PROP_FALLBACK:
456 image->priv->fallback_icon = g_value_dup_string (value);
457 break;
458 default:
459 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
460 break;
461 }
462 }
463
464 static void
465 rb_fading_image_init (RBFadingImage *image)
466 {
467 image->priv = G_TYPE_INSTANCE_GET_PRIVATE (image, RB_TYPE_FADING_IMAGE, RBFadingImagePrivate);
468 }
469
470 static void
471 rb_fading_image_class_init (RBFadingImageClass *klass)
472 {
473 GObjectClass *object_class = G_OBJECT_CLASS (klass);
474 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
475
476 object_class->constructed = impl_constructed;
477 object_class->dispose = impl_dispose;
478 object_class->finalize = impl_finalize;
479 object_class->set_property = impl_set_property;
480 object_class->get_property = impl_get_property;
481
482 widget_class->realize = impl_realize;
483 widget_class->draw = impl_draw;
484 widget_class->query_tooltip = impl_query_tooltip;
485 widget_class->drag_data_get = impl_drag_data_get;
486 widget_class->drag_data_received = impl_drag_data_received;
487
488 /**
489 * RBFadingImage:fallback:
490 *
491 * Name of an icon to display when no image is available.
492 */
493 g_object_class_install_property (object_class,
494 PROP_FALLBACK,
495 g_param_spec_string ("fallback",
496 "fallback",
497 "fallback icon name",
498 NULL,
499 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
500
501 /**
502 * RBFadingImage::uri-dropped
503 * @image: the #RBFadingImage
504 * @uri: the URI that was dropped
505 *
506 * Emitted when a URI is dragged and dropped on the image
507 */
508 signals[URI_DROPPED] =
509 g_signal_new ("uri-dropped",
510 G_OBJECT_CLASS_TYPE (object_class),
511 G_SIGNAL_RUN_LAST,
512 0,
513 NULL, NULL,
514 g_cclosure_marshal_VOID__STRING,
515 G_TYPE_NONE,
516 1, G_TYPE_STRING);
517
518 /**
519 * RBFadingImage::pixbuf-dropped
520 * @image: the #RBFadingImage
521 * @pixbuf: the pixbuf that was dropped
522 *
523 * Emitted when an image is dragged and dropped on the image
524 */
525 signals[PIXBUF_DROPPED] =
526 g_signal_new ("pixbuf-dropped",
527 G_OBJECT_CLASS_TYPE (object_class),
528 G_SIGNAL_RUN_LAST,
529 0,
530 NULL, NULL,
531 g_cclosure_marshal_VOID__OBJECT,
532 G_TYPE_NONE,
533 1, GDK_TYPE_PIXBUF);
534
535 g_type_class_add_private (klass, sizeof (RBFadingImagePrivate));
536 }
537
538
539 static GdkPixbuf *
540 scale_thumbnail_if_necessary (RBFadingImage *image, GdkPixbuf *pixbuf)
541 {
542 int w, h;
543 int pw, ph;
544 int sw, sh;
545 double factor;
546
547 w = gtk_widget_get_allocated_width (GTK_WIDGET (image)) - 2 * BORDER_WIDTH;
548 h = gtk_widget_get_allocated_height (GTK_WIDGET (image)) - 2 * BORDER_WIDTH;
549 pw = gdk_pixbuf_get_width (pixbuf);
550 ph = gdk_pixbuf_get_height (pixbuf);
551
552 if (pw <= w && ph <= h) {
553 return g_object_ref (pixbuf);
554 }
555
556 if (pw > ph) {
557 sw = w;
558 factor = (double) w / pw;
559 sh = (int)((double)ph * factor);
560 } else {
561 sh = h;
562 factor = (double) h / ph;
563 sw = (int)((double)pw * factor);
564 }
565
566 return gdk_pixbuf_scale_simple (pixbuf, sw, sh, GDK_INTERP_HYPER);
567 }
568
569 static GdkPixbuf *
570 scale_full_if_necessary (RBFadingImage *image, GdkPixbuf *pixbuf)
571 {
572 int pw, ph;
573 int sw, sh;
574 double factor;
575
576 pw = gdk_pixbuf_get_width (pixbuf);
577 ph = gdk_pixbuf_get_height (pixbuf);
578
579 if (pw <= MAX_TOOLTIP_SIZE && ph <= MAX_TOOLTIP_SIZE) {
580 return g_object_ref (pixbuf);
581 }
582 if (pw > ph) {
583 sw = MAX_TOOLTIP_SIZE;
584 factor = (double) MAX_TOOLTIP_SIZE / pw;
585 sh = (int)((double)ph * factor);
586 } else {
587 sh = MAX_TOOLTIP_SIZE;
588 factor = (double) MAX_TOOLTIP_SIZE / ph;
589 sw = (int)((double)pw * factor);
590 }
591
592 return gdk_pixbuf_scale_simple (pixbuf, sw, sh, GDK_INTERP_HYPER);
593 }
594
595
596 static void
597 clear_next (RBFadingImage *image)
598 {
599 if (image->priv->next_pat != NULL) {
600 cairo_pattern_destroy (image->priv->next_pat);
601 image->priv->next_pat = NULL;
602 }
603 if (image->priv->next != NULL) {
604 g_object_unref (image->priv->next);
605 image->priv->next = NULL;
606 }
607 if (image->priv->next_full != NULL) {
608 g_object_unref (image->priv->next_full);
609 image->priv->next_full = NULL;
610 }
611 image->priv->next_set = FALSE;
612 }
613
614 static void
615 replace_current (RBFadingImage *image, GdkPixbuf *next, GdkPixbuf *next_full)
616 {
617 if (image->priv->current_pat != NULL) {
618 cairo_pattern_destroy (image->priv->current_pat);
619 image->priv->current_pat = NULL;
620 }
621 if (image->priv->current != NULL) {
622 g_object_unref (image->priv->current);
623 image->priv->current = NULL;
624 }
625 if (image->priv->current_full != NULL) {
626 g_object_unref (image->priv->current_full);
627 image->priv->current_full = NULL;
628 }
629 if (next != NULL) {
630 image->priv->current = g_object_ref (next);
631 image->priv->current_width = gdk_pixbuf_get_width (image->priv->current);
632 image->priv->current_height = gdk_pixbuf_get_height (image->priv->current);
633 }
634 if (next_full != NULL) {
635 image->priv->current_full = g_object_ref (next_full);
636 }
637 }
638
639 static void
640 composite_into_current (RBFadingImage *image)
641 {
642 cairo_t *cr;
643 cairo_surface_t *dest;
644 int width;
645 int height;
646
647 width = gtk_widget_get_allocated_width (GTK_WIDGET (image)) - 2 * BORDER_WIDTH;
648 height = gtk_widget_get_allocated_height (GTK_WIDGET (image)) - 2 * BORDER_WIDTH;
649 dest = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height);
650
651 cr = cairo_create (dest);
652 render_current (image, cr, width, height, FALSE);
653 render_next (image, cr, width, height, FALSE);
654
655 if (image->priv->current_pat != NULL) {
656 cairo_pattern_destroy (image->priv->current_pat);
657 }
658 image->priv->current_pat = cairo_pattern_create_for_surface (dest);
659 image->priv->current_width = width;
660 image->priv->current_height = height;
661 }
662
663 /**
664 * rb_fading_image_set_pixbuf:
665 * @image: a #RBFadingImage
666 * @pixbuf: (transfer none): the next pixbuf to display
667 *
668 * Sets the next image to be displayed.
669 */
670 void
671 rb_fading_image_set_pixbuf (RBFadingImage *image, GdkPixbuf *pixbuf)
672 {
673 GdkPixbuf *scaled = NULL;
674 GdkPixbuf *full = NULL;
675
676 if (pixbuf != NULL) {
677 scaled = scale_thumbnail_if_necessary (image, pixbuf);
678 full = scale_full_if_necessary (image, pixbuf);
679 }
680
681 if (image->priv->render_timer_id != 0) {
682 composite_into_current (image);
683 clear_next (image);
684
685 image->priv->next_full = full;
686 image->priv->next = scaled;
687 image->priv->next_set = TRUE;
688 } else {
689 clear_next (image);
690 replace_current (image, scaled, full);
691 gtk_widget_queue_draw (GTK_WIDGET (image));
692 gtk_widget_trigger_tooltip_query (GTK_WIDGET (image));
693
694 if (scaled != NULL) {
695 g_object_unref (scaled);
696 }
697 if (full != NULL) {
698 g_object_unref (full);
699 }
700 }
701 }
702
703 static gboolean
704 render_timer (RBFadingImage *image)
705 {
706 gint64 now;
707
708 now = g_get_monotonic_time ();
709
710 /* calculate alpha, whether this is the last frame, etc. */
711 if (image->priv->next != NULL || image->priv->current != NULL) {
712 image->priv->alpha = (((double)now - image->priv->start) / (image->priv->end - image->priv->start));
713 if (image->priv->alpha > 1.0)
714 image->priv->alpha = 1.0;
715
716 gtk_widget_queue_draw (GTK_WIDGET (image));
717 }
718
719 if (now >= image->priv->end) {
720 replace_current (image, image->priv->next, image->priv->next_full);
721 clear_next (image);
722 gtk_widget_trigger_tooltip_query (GTK_WIDGET (image));
723 image->priv->alpha = 0.0;
724 image->priv->render_timer_id = 0;
725 return FALSE;
726 }
727
728 return TRUE;
729 }
730
731 static void
732 update_render_timer (RBFadingImage *image)
733 {
734 /* add timer if not already present */
735 if (image->priv->render_timer_id == 0) {
736 image->priv->render_timer_id = g_timeout_add (RENDER_FRAME_TIME,
737 (GSourceFunc) render_timer,
738 image);
739 }
740 }
741
742 /**
743 * rb_fading_image_start:
744 * @image: a #RBFadingImage
745 * @duration: length of fade in milliseconds
746 *
747 * Starts fading to the next image. If no next image has been supplied,
748 * the fallback image will be used instead. If the next image has been
749 * supplied, but has not finished loading yet, the fade will be delayed
750 * until it finishes. If the previous fade has not yet finished,
751 * something tricky happens.
752 */
753 void
754 rb_fading_image_start (RBFadingImage *image, guint64 duration)
755 {
756 image->priv->start = g_get_monotonic_time ();
757 image->priv->end = image->priv->start + (duration * 1000);
758
759 if (image->priv->next_set) {
760 replace_current (image, image->priv->next, image->priv->next_full);
761 clear_next (image);
762 image->priv->next_set = TRUE;
763 }
764
765 update_render_timer (image);
766 }