diff options
Diffstat (limited to 'libmate-desktop/mate-bg-crossfade.c')
-rw-r--r-- | libmate-desktop/mate-bg-crossfade.c | 542 |
1 files changed, 542 insertions, 0 deletions
diff --git a/libmate-desktop/mate-bg-crossfade.c b/libmate-desktop/mate-bg-crossfade.c new file mode 100644 index 0000000..5fa807f --- /dev/null +++ b/libmate-desktop/mate-bg-crossfade.c @@ -0,0 +1,542 @@ +/* mate-bg-crossfade.h - fade window background between two pixmaps + * + * Copyright (C) 2008 Red Hat, Inc. + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Author: Ray Strode <[email protected]> +*/ + +#include <string.h> +#include <math.h> +#include <stdarg.h> + +#include <gio/gio.h> + +#include <gdk/gdk.h> +#include <gdk/gdkx.h> +#include <X11/Xlib.h> +#include <X11/Xatom.h> +#include <gtk/gtk.h> + +#include <cairo.h> +#include <cairo-xlib.h> + +#define MATE_DESKTOP_USE_UNSTABLE_API +#include <libmateui/mate-bg.h> +#include "libmateui/mate-bg-crossfade.h" + +struct _MateBGCrossfadePrivate +{ + GdkWindow *window; + int width; + int height; + GdkPixmap *fading_pixmap; + GdkPixmap *end_pixmap; + gdouble start_time; + gdouble total_duration; + guint timeout_id; + guint is_first_frame : 1; +}; + +enum { + PROP_0, + PROP_WIDTH, + PROP_HEIGHT, +}; + +enum { + FINISHED, + NUMBER_OF_SIGNALS +}; + +static guint signals[NUMBER_OF_SIGNALS] = { 0 }; + +G_DEFINE_TYPE (MateBGCrossfade, mate_bg_crossfade, G_TYPE_OBJECT) +#define MATE_BG_CROSSFADE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o),\ + MATE_TYPE_BG_CROSSFADE,\ + MateBGCrossfadePrivate)) + +static void +mate_bg_crossfade_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + MateBGCrossfade *fade; + + g_assert (MATE_IS_BG_CROSSFADE (object)); + + fade = MATE_BG_CROSSFADE (object); + + switch (property_id) + { + case PROP_WIDTH: + fade->priv->width = g_value_get_int (value); + break; + case PROP_HEIGHT: + fade->priv->height = g_value_get_int (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +mate_bg_crossfade_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + MateBGCrossfade *fade; + + g_assert (MATE_IS_BG_CROSSFADE (object)); + + fade = MATE_BG_CROSSFADE (object); + + switch (property_id) + { + case PROP_WIDTH: + g_value_set_int (value, fade->priv->width); + break; + case PROP_HEIGHT: + g_value_set_int (value, fade->priv->height); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +mate_bg_crossfade_finalize (GObject *object) +{ + MateBGCrossfade *fade; + + fade = MATE_BG_CROSSFADE (object); + + mate_bg_crossfade_stop (fade); + + if (fade->priv->fading_pixmap != NULL) { + g_object_unref (fade->priv->fading_pixmap); + fade->priv->fading_pixmap = NULL; + } + + if (fade->priv->end_pixmap != NULL) { + g_object_unref (fade->priv->end_pixmap); + fade->priv->end_pixmap = NULL; + } +} + +static void +mate_bg_crossfade_class_init (MateBGCrossfadeClass *fade_class) +{ + GObjectClass *gobject_class; + + gobject_class = G_OBJECT_CLASS (fade_class); + + gobject_class->get_property = mate_bg_crossfade_get_property; + gobject_class->set_property = mate_bg_crossfade_set_property; + gobject_class->finalize = mate_bg_crossfade_finalize; + + /** + * MateBGCrossfade:width: + * + * When a crossfade is running, this is width of the fading + * pixmap. + */ + g_object_class_install_property (gobject_class, + PROP_WIDTH, + g_param_spec_int ("width", + "Window Width", + "Width of window to fade", + 0, G_MAXINT, 0, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); + + /** + * MateBGCrossfade:height: + * + * When a crossfade is running, this is height of the fading + * pixmap. + */ + g_object_class_install_property (gobject_class, + PROP_HEIGHT, + g_param_spec_int ("height", "Window Height", + "Height of window to fade on", + 0, G_MAXINT, 0, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); + + /** + * MateBGCrossfade::finished: + * @fade: the #MateBGCrossfade that received the signal + * @window: the #GdkWindow the crossfade happend on. + * + * When a crossfade finishes, @window will have a copy + * of the end pixmap as its background, and this signal will + * get emitted. + */ + signals[FINISHED] = g_signal_new ("finished", + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, G_TYPE_OBJECT); + + g_type_class_add_private (gobject_class, sizeof (MateBGCrossfadePrivate)); +} + +static void +mate_bg_crossfade_init (MateBGCrossfade *fade) +{ + fade->priv = MATE_BG_CROSSFADE_GET_PRIVATE (fade); + + fade->priv->fading_pixmap = NULL; + fade->priv->end_pixmap = NULL; + fade->priv->timeout_id = 0; +} + +/** + * mate_bg_crossfade_new: + * @width: The width of the crossfading window + * @height: The height of the crossfading window + * + * Creates a new object to manage crossfading a + * window background between two #GdkPixmap drawables. + * + * Return value: the new #MateBGCrossfade + **/ +MateBGCrossfade * +mate_bg_crossfade_new (int width, + int height) +{ + GObject *object; + + object = g_object_new (MATE_TYPE_BG_CROSSFADE, + "width", width, + "height", height, NULL); + + return (MateBGCrossfade *) object; +} + +static GdkPixmap * +tile_pixmap (GdkPixmap *pixmap, + int width, + int height) +{ + GdkPixmap *copy; + cairo_t *cr; + + copy = gdk_pixmap_new (pixmap, width, height, pixmap == NULL? 24 : -1); + + cr = gdk_cairo_create (copy); + + if (pixmap != NULL) { + cairo_pattern_t *pattern; + gdk_cairo_set_source_pixmap (cr, pixmap, 0.0, 0.0); + pattern = cairo_get_source (cr); + cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT); + } else { + GtkStyle *style; + style = gtk_widget_get_default_style (); + gdk_cairo_set_source_color (cr, &style->bg[GTK_STATE_NORMAL]); + } + + cairo_paint (cr); + + if (cairo_status (cr) != CAIRO_STATUS_SUCCESS) { + g_object_unref (copy); + copy = NULL; + } + cairo_destroy (cr); + + return copy; +} + +/** + * mate_bg_crossfade_set_start_pixmap: + * @fade: a #MateBGCrossfade + * @pixmap: The #GdkPixmap to fade from + * + * Before initiating a crossfade with mate_bg_crossfade_start() + * a start and end pixmap have to be set. This function sets + * the pixmap shown at the beginning of the crossfade effect. + * + * Return value: %TRUE if successful, or %FALSE if the pixmap + * could not be copied. + **/ +gboolean +mate_bg_crossfade_set_start_pixmap (MateBGCrossfade *fade, + GdkPixmap *pixmap) +{ + g_return_val_if_fail (MATE_IS_BG_CROSSFADE (fade), FALSE); + + if (fade->priv->fading_pixmap != NULL) { + g_object_unref (fade->priv->fading_pixmap); + fade->priv->fading_pixmap = NULL; + } + + fade->priv->fading_pixmap = tile_pixmap (pixmap, + fade->priv->width, + fade->priv->height); + + return fade->priv->fading_pixmap != NULL; +} + +static gdouble +get_current_time (void) +{ + const double microseconds_per_second = (double) G_USEC_PER_SEC; + double timestamp; + GTimeVal now; + + g_get_current_time (&now); + + timestamp = ((microseconds_per_second * now.tv_sec) + now.tv_usec) / + microseconds_per_second; + + return timestamp; +} + +/** + * mate_bg_crossfade_set_end_pixmap: + * @fade: a #MateBGCrossfade + * @pixmap: The #GdkPixmap to fade to + * + * Before initiating a crossfade with mate_bg_crossfade_start() + * a start and end pixmap have to be set. This function sets + * the pixmap shown at the end of the crossfade effect. + * + * Return value: %TRUE if successful, or %FALSE if the pixmap + * could not be copied. + **/ +gboolean +mate_bg_crossfade_set_end_pixmap (MateBGCrossfade *fade, + GdkPixmap *pixmap) +{ + g_return_val_if_fail (MATE_IS_BG_CROSSFADE (fade), FALSE); + + if (fade->priv->end_pixmap != NULL) { + g_object_unref (fade->priv->end_pixmap); + fade->priv->end_pixmap = NULL; + } + + fade->priv->end_pixmap = tile_pixmap (pixmap, + fade->priv->width, + fade->priv->height); + + /* Reset timer in case we're called while animating + */ + fade->priv->start_time = get_current_time (); + return fade->priv->end_pixmap != NULL; +} + +static gboolean +animations_are_disabled (MateBGCrossfade *fade) +{ + GtkSettings *settings; + GdkScreen *screen; + gboolean are_enabled; + + g_assert (fade->priv->window != NULL); + + screen = gdk_drawable_get_screen (fade->priv->window); + + settings = gtk_settings_get_for_screen (screen); + + g_object_get (settings, "gtk-enable-animations", &are_enabled, NULL); + + return !are_enabled; +} + +static void +draw_background (MateBGCrossfade *fade) +{ + if (GDK_WINDOW_TYPE (fade->priv->window) == GDK_WINDOW_ROOT) { + GdkDisplay *display; + display = gdk_drawable_get_display (fade->priv->window); + gdk_window_clear (fade->priv->window); + gdk_flush (); + } else { + gdk_window_invalidate_rect (fade->priv->window, NULL, FALSE); + gdk_window_process_updates (fade->priv->window, FALSE); + } +} + +static gboolean +on_tick (MateBGCrossfade *fade) +{ + gdouble now, percent_done; + cairo_t *cr; + cairo_status_t status; + + g_return_val_if_fail (MATE_IS_BG_CROSSFADE (fade), FALSE); + + now = get_current_time (); + + percent_done = (now - fade->priv->start_time) / fade->priv->total_duration; + percent_done = CLAMP (percent_done, 0.0, 1.0); + + /* If it's taking a long time to get to the first frame, + * then lengthen the duration, so the user will get to see + * the effect. + */ + if (fade->priv->is_first_frame && percent_done > .33) { + fade->priv->is_first_frame = FALSE; + fade->priv->total_duration *= 1.5; + return on_tick (fade); + } + + if (fade->priv->fading_pixmap == NULL) { + return FALSE; + } + + if (animations_are_disabled (fade)) { + return FALSE; + } + + /* We accumulate the results in place for performance reasons. + * + * This means 1) The fade is exponential, not linear (looks good!) + * 2) The rate of fade is not independent of frame rate. Slower machines + * will get a slower fade (but never longer than .75 seconds), and + * even the fastest machines will get *some* fade because the framerate + * is capped. + */ + cr = gdk_cairo_create (fade->priv->fading_pixmap); + + gdk_cairo_set_source_pixmap (cr, fade->priv->end_pixmap, + 0.0, 0.0); + cairo_paint_with_alpha (cr, percent_done); + + status = cairo_status (cr); + cairo_destroy (cr); + + if (status == CAIRO_STATUS_SUCCESS) { + draw_background (fade); + } + return percent_done <= .99; +} + +static void +on_finished (MateBGCrossfade *fade) +{ + if (fade->priv->timeout_id == 0) + return; + + g_assert (fade->priv->end_pixmap != NULL); + + gdk_window_set_back_pixmap (fade->priv->window, + fade->priv->end_pixmap, + FALSE); + draw_background (fade); + + g_object_unref (fade->priv->end_pixmap); + fade->priv->end_pixmap = NULL; + + g_assert (fade->priv->fading_pixmap != NULL); + + g_object_unref (fade->priv->fading_pixmap); + fade->priv->fading_pixmap = NULL; + + fade->priv->timeout_id = 0; + g_signal_emit (fade, signals[FINISHED], 0, fade->priv->window); +} + +/** + * mate_bg_crossfade_start: + * @fade: a #MateBGCrossfade + * @window: The #GdkWindow to draw crossfade on + * + * This function initiates a quick crossfade between two pixmaps on + * the background of @window. Before initiating the crossfade both + * mate_bg_crossfade_start() and mate_bg_crossfade_end() need to + * be called. If animations are disabled, the crossfade is skipped, + * and the window background is set immediately to the end pixmap. + **/ +void +mate_bg_crossfade_start (MateBGCrossfade *fade, + GdkWindow *window) +{ + GSource *source; + GMainContext *context; + + g_return_if_fail (MATE_IS_BG_CROSSFADE (fade)); + g_return_if_fail (window != NULL); + g_return_if_fail (fade->priv->fading_pixmap != NULL); + g_return_if_fail (fade->priv->end_pixmap != NULL); + g_return_if_fail (!mate_bg_crossfade_is_started (fade)); + g_return_if_fail (GDK_WINDOW_TYPE (window) != GDK_WINDOW_FOREIGN); + + source = g_timeout_source_new (1000 / 60.0); + g_source_set_callback (source, + (GSourceFunc) on_tick, + fade, + (GDestroyNotify) on_finished); + context = g_main_context_default (); + fade->priv->timeout_id = g_source_attach (source, context); + g_source_unref (source); + + fade->priv->window = window; + gdk_window_set_back_pixmap (fade->priv->window, + fade->priv->fading_pixmap, + FALSE); + draw_background (fade); + + fade->priv->is_first_frame = TRUE; + fade->priv->total_duration = .75; + fade->priv->start_time = get_current_time (); +} + + +/** + * mate_bg_crossfade_is_started: + * @fade: a #MateBGCrossfade + * + * This function reveals whether or not @fade is currently + * running on a window. See mate_bg_crossfade_start() for + * information on how to initiate a crossfade. + * + * Return value: %TRUE if fading, or %FALSE if not fading + **/ +gboolean +mate_bg_crossfade_is_started (MateBGCrossfade *fade) +{ + g_return_val_if_fail (MATE_IS_BG_CROSSFADE (fade), FALSE); + + return fade->priv->timeout_id != 0; +} + +/** + * mate_bg_crossfade_stop: + * @fade: a #MateBGCrossfade + * + * This function stops any in progress crossfades that may be + * happening. It's harmless to call this function if @fade is + * already stopped. + **/ +void +mate_bg_crossfade_stop (MateBGCrossfade *fade) +{ + g_return_if_fail (MATE_IS_BG_CROSSFADE (fade)); + + if (!mate_bg_crossfade_is_started (fade)) + return; + + g_assert (fade->priv->timeout_id != 0); + g_source_remove (fade->priv->timeout_id); + fade->priv->timeout_id = 0; +} |