diff options
Diffstat (limited to 'savers/floaters.c')
-rw-r--r-- | savers/floaters.c | 1269 |
1 files changed, 1269 insertions, 0 deletions
diff --git a/savers/floaters.c b/savers/floaters.c new file mode 100644 index 0000000..8052e95 --- /dev/null +++ b/savers/floaters.c @@ -0,0 +1,1269 @@ +/* + * Copyright (C) 2005 Ray Strode <[email protected]>, + * Matthias Clasen <[email protected]>, + * Søren Sandmann <[email protected]> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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. + * + * Originally written by: Ray Strode <[email protected]> + * + * Later contributions by: Matthias Clasen <[email protected]> + * Søren Sandmann <[email protected]> + */ + +#include "config.h" + +#include <math.h> +#include <stdlib.h> +#include <sysexits.h> +#include <time.h> + +#include <glib.h> +#include <glib/gi18n.h> + +#include <gdk/gdk.h> +#include <gdk/gdkx.h> + +#include <gtk/gtk.h> + +#include "gs-theme-window.h" + +#ifndef trunc +#define trunc(x) (((x) > 0.0) ? floor((x)) : -floor(-(x))) +#endif + +#ifndef OPTIMAL_FRAME_RATE +#define OPTIMAL_FRAME_RATE (25.0) +#endif + +#ifndef STAT_PRINT_FREQUENCY +#define STAT_PRINT_FREQUENCY (2000) +#endif + +#ifndef FLOATER_MAX_SIZE +#define FLOATER_MAX_SIZE (128.0) +#endif + +#ifndef FLOATER_MIN_SIZE +#define FLOATER_MIN_SIZE (16.0) +#endif +#ifndef FLOATER_DEFAULT_COUNT +#define FLOATER_DEFAULT_COUNT (5) +#endif + +#ifndef SMALL_ANGLE +#define SMALL_ANGLE (0.025 * G_PI) +#endif + +#ifndef BIG_ANGLE +#define BIG_ANGLE (0.125 * G_PI) +#endif + +#ifndef GAMMA +#define GAMMA 2.2 +#endif + +static gboolean should_show_paths = FALSE; +static gboolean should_do_rotations = FALSE; +static gboolean should_print_stats = FALSE; +static gint max_floater_count = FLOATER_DEFAULT_COUNT; +static gchar *geometry = NULL; +static gchar **filenames = NULL; + +static GOptionEntry options[] = +{ + { + "show-paths", 'p', 0, G_OPTION_ARG_NONE, &should_show_paths, + N_("Show paths that images follow"), NULL + }, + + { + "do-rotations", 'r', 0, G_OPTION_ARG_NONE, &should_do_rotations, + N_("Occasionally rotate images as they move"), NULL + }, + + { + "print-stats", 's', 0, G_OPTION_ARG_NONE, &should_print_stats, + N_("Print out frame rate and other statistics"), NULL + }, + + { + "number-of-images", 'n', 0, G_OPTION_ARG_INT, &max_floater_count, + N_("The maximum number of images to keep on screen"), N_("MAX_IMAGES") + }, + + { + "geometry", 0, 0, G_OPTION_ARG_STRING, &geometry, + N_("The initial size and position of window"), N_("WIDTHxHEIGHT+X+Y") + }, + + { + G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames, + N_("The source image to use"), NULL + }, + + {NULL} +}; + + +typedef struct _Point Point; +typedef struct _Path Path; +typedef struct _Rectangle Rectangle; +typedef struct _ScreenSaverFloater ScreenSaverFloater; +typedef struct _CachedSource CachedSource; +typedef struct _ScreenSaver ScreenSaver; + +struct _Point +{ + gdouble x, y; +}; + +struct _Path +{ + Point start_point; + Point start_control_point; + Point end_control_point; + Point end_point; + + gdouble x_linear_coefficient, + y_linear_coefficient; + + gdouble x_quadratic_coefficient, + y_quadratic_coefficient; + + gdouble x_cubic_coefficient, + y_cubic_coefficient; + + gdouble duration; +}; + +struct _CachedSource +{ + cairo_pattern_t *pattern; + gint width, height; +}; + +struct _Rectangle +{ + Point top_left_point; + Point bottom_right_point; +}; + +struct _ScreenSaverFloater +{ + GdkRectangle bounds; + + Point start_position; + Point position; + + gdouble scale; + gdouble opacity; + + Path *path; + gdouble path_start_time; + gdouble path_start_scale; + gdouble path_end_scale; + + gdouble angle; + gdouble angle_increment; +}; + +struct _ScreenSaver +{ + GtkWidget *drawing_area; + Rectangle canvas_rectangle; + GHashTable *cached_sources; + + char *filename; + + gdouble first_update_time; + + gdouble last_calculated_stats_time, + current_calculated_stats_time; + gint update_count, frame_count; + + gdouble updates_per_second; + gdouble frames_per_second; + + guint state_update_timeout_id; + guint stats_update_timeout_id; + + GList *floaters; + gint max_floater_count; + + guint should_do_rotations: 1; + guint should_show_paths : 1; + guint draw_ops_pending : 1; +}; + +static Path *path_new (Point *start_point, + Point *start_control_point, + Point *end_control_point, + Point *end_point, + gdouble duration); +static void path_free (Path *path); + +static ScreenSaverFloater *screen_saver_floater_new (ScreenSaver *screen_saver, + Point *position, + gdouble scale); +static void screen_saver_floater_free (ScreenSaver *screen_saver, + ScreenSaverFloater *floater); +static gboolean screen_saver_floater_is_off_canvas (ScreenSaver *screen_saver, + ScreenSaverFloater *floater); +static gboolean screen_saver_floater_should_bubble_up (ScreenSaver *screen_saver, + ScreenSaverFloater *floater, + gdouble performance_ratio, + gdouble *duration); +static gboolean screen_saver_floater_should_come_on_screen (ScreenSaver *screen_saver, + ScreenSaverFloater *floater, + gdouble performance_ratio, + gdouble *duration); +static Point screen_saver_floater_get_position_from_time (ScreenSaver *screen_saver, + ScreenSaverFloater *floater, + gdouble time); +static gdouble screen_saver_floater_get_scale_from_time (ScreenSaver *screen_saver, + ScreenSaverFloater *floater, + gdouble time); +static gdouble screen_saver_floater_get_angle_from_time (ScreenSaver *screen_saver, + ScreenSaverFloater *floater, + gdouble time); + +static Path *screen_saver_floater_create_path_to_on_screen (ScreenSaver *screen_saver, + ScreenSaverFloater *floater, + gdouble duration); +static Path *screen_saver_floater_create_path_to_bubble_up (ScreenSaver *screen_saver, + ScreenSaverFloater *floater, + gdouble duration); +static Path *screen_saver_floater_create_path_to_random_point (ScreenSaver *screen_saver, + ScreenSaverFloater *floater, + gdouble duration); +static Path *screen_saver_floater_create_path (ScreenSaver *screen_saver, + ScreenSaverFloater *floater); +static void screen_saver_floater_update_state (ScreenSaver *screen_saver, + ScreenSaverFloater *floater, + gdouble time); + +static gboolean screen_saver_floater_do_draw (ScreenSaver *screen_saver, + ScreenSaverFloater *floater, + cairo_t *context); + +static CachedSource *cached_source_new (cairo_pattern_t *pattern, + gint width, + gint height); +static void cached_source_free (CachedSource *source); + +static ScreenSaver *screen_saver_new (GtkDrawingArea *drawing_area, + const gchar *filename, + gint max_floater_count, + gboolean should_do_rotations, + gboolean should_show_paths); +static void screen_saver_free (ScreenSaver *screen_saver); +static gdouble screen_saver_get_timestamp (ScreenSaver *screen_saver); +static void screen_saver_get_initial_state (ScreenSaver *screen_saver); +static void screen_saver_update_state (ScreenSaver *screen_saver, + gdouble time); +static gboolean screen_saver_do_update_state (ScreenSaver *screen_saver); +static gboolean screen_saver_do_update_stats (ScreenSaver *screen_saver); +static gdouble screen_saver_get_updates_per_second (ScreenSaver *screen_saver); +static gdouble screen_saver_get_frames_per_second (ScreenSaver *screen_saver); +static gdouble screen_saver_get_image_cache_usage (ScreenSaver *screen_saver); +static void screen_saver_create_floaters (ScreenSaver *screen_saver); +static void screen_saver_destroy_floaters (ScreenSaver *screen_saver); +static void screen_saver_on_size_allocate (ScreenSaver *screen_saver, + GtkAllocation *allocation); +static void screen_saver_on_expose_event (ScreenSaver *screen_saver, + GdkEventExpose *event); +static gboolean do_print_screen_saver_stats (ScreenSaver *screen_saver); +static GdkPixbuf *gamma_correct (const GdkPixbuf *input_pixbuf); + +static CachedSource* +cached_source_new (cairo_pattern_t *pattern, + gint width, + gint height) +{ + CachedSource *source; + + source = g_new (CachedSource, 1); + source->pattern = cairo_pattern_reference (pattern); + source->width = width; + source->height = height; + + return source; +} + +static void +cached_source_free (CachedSource *source) +{ + if (source == NULL) + return; + + cairo_pattern_destroy (source->pattern); + + g_free (source); +} + +static Path * +path_new (Point *start_point, + Point *start_control_point, + Point *end_control_point, + Point *end_point, + gdouble duration) +{ + Path *path; + + path = g_new (Path, 1); + path->start_point = *start_point; + path->start_control_point = *start_control_point; + path->end_control_point = *end_control_point; + path->end_point = *end_point; + path->duration = duration; + + /* we precompute the coefficients to the cubic bezier curve here + * so that we don't have to do it repeatedly later The equation is: + * + * B(t) = A * t^3 + B * t^2 + C * t + start_point + */ + path->x_linear_coefficient = 3 * (start_control_point->x - start_point->x); + path->x_quadratic_coefficient = 3 * (end_control_point->x - + start_control_point->x) - + path->x_linear_coefficient; + path->x_cubic_coefficient = end_point->x - start_point->x - + path->x_linear_coefficient - + path->x_quadratic_coefficient; + + path->y_linear_coefficient = 3 * (start_control_point->y - start_point->y); + path->y_quadratic_coefficient = 3 * (end_control_point->y - + start_control_point->y) - + path->y_linear_coefficient; + path->y_cubic_coefficient = end_point->y - start_point->y - + path->y_linear_coefficient - + path->y_quadratic_coefficient; + return path; +} + +static void +path_free (Path *path) +{ + g_free (path); +} + +static ScreenSaverFloater* +screen_saver_floater_new (ScreenSaver *screen_saver, + Point *position, + gdouble scale) +{ + ScreenSaverFloater *floater; + + floater = g_new (ScreenSaverFloater, 1); + floater->bounds.width = 0; + floater->start_position = *position; + floater->position = *position; + floater->scale = scale; + floater->opacity = pow (scale, 1.0 / GAMMA); + floater->path = NULL; + floater->path_start_time = 0.0; + floater->path_start_scale = 1.0; + floater->path_end_scale = 0.0; + + floater->angle = 0.0; + floater->angle_increment = 0.0; + + return floater; +} + +void +screen_saver_floater_free (ScreenSaver *screen_saver, + ScreenSaverFloater *floater) +{ + if (floater == NULL) + return; + + path_free (floater->path); + + g_free (floater); +} + +static gboolean +screen_saver_floater_is_off_canvas (ScreenSaver *screen_saver, + ScreenSaverFloater *floater) +{ + if ((floater->position.x < screen_saver->canvas_rectangle.top_left_point.x) || + (floater->position.x > screen_saver->canvas_rectangle.bottom_right_point.x) || + (floater->position.y < screen_saver->canvas_rectangle.top_left_point.y) || + (floater->position.y > screen_saver->canvas_rectangle.bottom_right_point.y)) + return TRUE; + + return FALSE; +} + +static gboolean +screen_saver_floater_should_come_on_screen (ScreenSaver *screen_saver, + ScreenSaverFloater *floater, + gdouble performance_ratio, + gdouble *duration) +{ + + if (!screen_saver_floater_is_off_canvas (screen_saver, floater)) + return FALSE; + + if ((abs (performance_ratio - .5) >= G_MINDOUBLE) && + (g_random_double () > .5)) + { + if (duration) + *duration = g_random_double_range (3.0, 7.0); + + return TRUE; + } + + return FALSE; +} + +static gboolean +screen_saver_floater_should_bubble_up (ScreenSaver *screen_saver, + ScreenSaverFloater *floater, + gdouble performance_ratio, + gdouble *duration) +{ + + if ((performance_ratio < .5) && (g_random_double () > .5)) + { + if (duration) + *duration = performance_ratio * 30.0; + + return TRUE; + } + + if ((floater->scale < .3) && (g_random_double () > .6)) + { + if (duration) + *duration = 30.0; + + return TRUE; + } + + return FALSE; +} + +static Point +screen_saver_floater_get_position_from_time (ScreenSaver *screen_saver, + ScreenSaverFloater *floater, + gdouble time) +{ + Point point; + + time = time / floater->path->duration; + + point.x = floater->path->x_cubic_coefficient * (time * time * time) + + floater->path->x_quadratic_coefficient * (time * time) + + floater->path->x_linear_coefficient * (time) + + floater->path->start_point.x; + point.y = floater->path->y_cubic_coefficient * (time * time * time) + + floater->path->y_quadratic_coefficient * (time * time) + + floater->path->y_linear_coefficient * (time) + + floater->path->start_point.y; + + return point; +} + +static gdouble +screen_saver_floater_get_scale_from_time (ScreenSaver *screen_saver, + ScreenSaverFloater *floater, + gdouble time) +{ + gdouble completion_ratio, total_scale_growth, new_scale; + + completion_ratio = time / floater->path->duration; + total_scale_growth = (floater->path_end_scale - floater->path_start_scale); + new_scale = floater->path_start_scale + total_scale_growth * completion_ratio; + + return CLAMP (new_scale, 0.0, 1.0); +} + +static gdouble +screen_saver_floater_get_angle_from_time (ScreenSaver *screen_saver, + ScreenSaverFloater *floater, + gdouble time) +{ + gdouble completion_ratio; + gdouble total_rotation; + + completion_ratio = time / floater->path->duration; + total_rotation = floater->angle_increment * floater->path->duration; + + return floater->angle + total_rotation * completion_ratio; +} + +static Path * +screen_saver_floater_create_path_to_on_screen (ScreenSaver *screen_saver, + ScreenSaverFloater *floater, + gdouble duration) +{ + Point start_position, end_position, start_control_point, end_control_point; + start_position = floater->position; + + end_position.x = g_random_double_range (.25, .75) * + (screen_saver->canvas_rectangle.top_left_point.x + + screen_saver->canvas_rectangle.bottom_right_point.x); + end_position.y = g_random_double_range (.25, .75) * + (screen_saver->canvas_rectangle.top_left_point.y + + screen_saver->canvas_rectangle.bottom_right_point.y); + + start_control_point.x = start_position.x + .9 * (end_position.x - start_position.x); + start_control_point.y = start_position.y + .9 * (end_position.y - start_position.y); + + end_control_point.x = start_position.x + 1.0 * (end_position.x - start_position.x); + end_control_point.y = start_position.y + 1.0 * (end_position.y - start_position.y); + + return path_new (&start_position, &start_control_point, &end_control_point, + &end_position, duration); +} + +static Path * +screen_saver_floater_create_path_to_bubble_up (ScreenSaver *screen_saver, + ScreenSaverFloater *floater, + gdouble duration) +{ + Point start_position, end_position, start_control_point, end_control_point; + + start_position = floater->position; + end_position.x = start_position.x; + end_position.y = screen_saver->canvas_rectangle.top_left_point.y - FLOATER_MAX_SIZE; + start_control_point.x = .5 * start_position.x; + start_control_point.y = .5 * start_position.y; + end_control_point.x = 1.5 * end_position.x; + end_control_point.y = .5 * end_position.y; + + return path_new (&start_position, &start_control_point, &end_control_point, + &end_position, duration); +} + +static Path * +screen_saver_floater_create_path_to_random_point (ScreenSaver *screen_saver, + ScreenSaverFloater *floater, + gdouble duration) +{ + Point start_position, end_position, start_control_point, end_control_point; + + start_position = floater->position; + + end_position.x = start_position.x + + (g_random_double_range (-.5, .5) * 4 * FLOATER_MAX_SIZE); + end_position.y = start_position.y + + (g_random_double_range (-.5, .5) * 4 * FLOATER_MAX_SIZE); + + start_control_point.x = start_position.x + .95 * (end_position.x - start_position.x); + start_control_point.y = start_position.y + .95 * (end_position.y - start_position.y); + + end_control_point.x = start_position.x + 1.0 * (end_position.x - start_position.x); + end_control_point.y = start_position.y + 1.0 * (end_position.y - start_position.y); + + return path_new (&start_position, &start_control_point, &end_control_point, + &end_position, duration); +} + +static Path * +screen_saver_floater_create_path (ScreenSaver *screen_saver, + ScreenSaverFloater *floater) +{ + gdouble performance_ratio; + gdouble duration; + + performance_ratio = + screen_saver_get_frames_per_second (screen_saver) / OPTIMAL_FRAME_RATE; + + if (abs (performance_ratio) <= G_MINDOUBLE) + performance_ratio = 1.0; + + if (screen_saver_floater_should_bubble_up (screen_saver, floater, performance_ratio, &duration)) + return screen_saver_floater_create_path_to_bubble_up (screen_saver, floater, duration); + + if (screen_saver_floater_should_come_on_screen (screen_saver, floater, performance_ratio, &duration)) + return screen_saver_floater_create_path_to_on_screen (screen_saver, floater, duration); + + return screen_saver_floater_create_path_to_random_point (screen_saver, floater, + g_random_double_range (3.0, 7.0)); +} + +static void +screen_saver_floater_update_state (ScreenSaver *screen_saver, + ScreenSaverFloater *floater, + gdouble time) +{ + gdouble performance_ratio; + + performance_ratio = + screen_saver_get_frames_per_second (screen_saver) / OPTIMAL_FRAME_RATE; + + if (floater->path == NULL) + { + floater->path = screen_saver_floater_create_path (screen_saver, floater); + floater->path_start_time = time; + + floater->path_start_scale = floater->scale; + + if (g_random_double () > .5) + floater->path_end_scale = g_random_double_range (0.10, performance_ratio); + + /* poor man's distribution */ + if (screen_saver->should_do_rotations && + (g_random_double () < .75 * performance_ratio)) + { + gint r; + + r = g_random_int_range (0, 100); + if (r < 80) + floater->angle_increment = 0.0; + else if (r < 95) + floater->angle_increment = g_random_double_range (-SMALL_ANGLE, SMALL_ANGLE); + else + floater->angle_increment = g_random_double_range (-BIG_ANGLE, BIG_ANGLE); + } + } + + if (time < (floater->path_start_time + floater->path->duration)) + { + gdouble path_time; + + path_time = time - floater->path_start_time; + + floater->position = + screen_saver_floater_get_position_from_time (screen_saver, floater, + path_time); + floater->scale = + screen_saver_floater_get_scale_from_time (screen_saver, floater, path_time); + + floater->angle = + screen_saver_floater_get_angle_from_time (screen_saver, floater, path_time); + + floater->opacity = pow (floater->scale, 1.0 / GAMMA); + } + else + { + path_free (floater->path); + + floater->path = NULL; + floater->path_start_time = 0.0; + } +} + +static GdkPixbuf * +gamma_correct (const GdkPixbuf *input_pixbuf) +{ + gint x, y, width, height, rowstride; + GdkPixbuf *output_pixbuf; + guchar *pixels; + + output_pixbuf = gdk_pixbuf_copy (input_pixbuf); + pixels = gdk_pixbuf_get_pixels (output_pixbuf); + + width = gdk_pixbuf_get_width (output_pixbuf); + height = gdk_pixbuf_get_height (output_pixbuf); + rowstride = gdk_pixbuf_get_rowstride (output_pixbuf); + + for (y = 0; y < height; y++) + for (x = 0; x < width; x++) + { + guchar *alpha_channel; + guchar opacity; + + alpha_channel = pixels + y * (rowstride / 4) + x + 3; + opacity = (guchar) (255 * pow ((*alpha_channel / 255.0), 1.0 / GAMMA)); + + *alpha_channel = opacity; + } + + return output_pixbuf; +} + +static gboolean +screen_saver_floater_do_draw (ScreenSaver *screen_saver, + ScreenSaverFloater *floater, + cairo_t *context) +{ + gint size; + CachedSource *source; + + size = CLAMP ((int) (FLOATER_MAX_SIZE * floater->scale), + FLOATER_MIN_SIZE, FLOATER_MAX_SIZE); + + source = g_hash_table_lookup (screen_saver->cached_sources, GINT_TO_POINTER (size)); + + if (source == NULL) + { + GdkPixbuf *pixbuf; + GError *error; + + pixbuf = NULL; + error = NULL; + + pixbuf = gdk_pixbuf_new_from_file_at_size (screen_saver->filename, size, -1, + &error); + if (pixbuf == NULL) + { + g_assert (error != NULL); + g_printerr ("%s", _(error->message)); + g_error_free (error); + return FALSE; + } + + if (gdk_pixbuf_get_has_alpha (pixbuf)) + gamma_correct (pixbuf); + + gdk_cairo_set_source_pixbuf (context, pixbuf, 0.0, 0.0); + + source = cached_source_new (cairo_get_source (context), + gdk_pixbuf_get_width (pixbuf), + gdk_pixbuf_get_height (pixbuf)); + g_object_unref (pixbuf); + g_hash_table_insert (screen_saver->cached_sources, GINT_TO_POINTER (size), + source); + } + + cairo_save (context); + + if (screen_saver->should_do_rotations && (abs (floater->angle) > G_MINDOUBLE)) + { + floater->bounds.width = G_SQRT2 * source->width + 2; + floater->bounds.height = G_SQRT2 * source->height + 2; + floater->bounds.x = (int) (floater->position.x - .5 * G_SQRT2 * source->width) - 1; + floater->bounds.y = (int) (floater->position.y - .5 * G_SQRT2 * source->height) - 1; + + cairo_translate (context, + trunc (floater->position.x), + trunc (floater->position.y)); + cairo_rotate (context, floater->angle); + cairo_translate (context, + -trunc (floater->position.x), + -trunc (floater->position.y)); + } + else + { + floater->bounds.width = source->width + 2; + floater->bounds.height = source->height + 2; + floater->bounds.x = (int) (floater->position.x - .5 * source->width) - 1; + floater->bounds.y = (int) (floater->position.y - .5 * source->height) - 1; + } + + cairo_translate (context, + trunc (floater->position.x - .5 * source->width), + trunc (floater->position.y - .5 * source->height)); + + cairo_set_source (context, source->pattern); + + cairo_rectangle (context, + trunc (.5 * (source->width - floater->bounds.width)), + trunc (.5 * (source->height - floater->bounds.height)), + floater->bounds.width, floater->bounds.height); + + cairo_clip (context); + cairo_paint_with_alpha (context, floater->opacity); + cairo_restore (context); + + if (screen_saver->should_show_paths && (floater->path != NULL)) + { + gdouble dash_pattern[] = { 5.0 }; + gint size; + + size = CLAMP ((int) (FLOATER_MAX_SIZE * floater->path_start_scale), + FLOATER_MIN_SIZE, FLOATER_MAX_SIZE); + + cairo_save (context); + cairo_set_source_rgba (context, 1.0, 1.0, 1.0, .2 * floater->opacity); + cairo_move_to (context, + floater->path->start_point.x, + floater->path->start_point.y); + cairo_curve_to (context, + floater->path->start_control_point.x, + floater->path->start_control_point.y, + floater->path->end_control_point.x, + floater->path->end_control_point.y, + floater->path->end_point.x, + floater->path->end_point.y); + cairo_set_line_cap (context, CAIRO_LINE_CAP_ROUND); + cairo_stroke (context); + cairo_set_source_rgba (context, 1.0, 0.0, 0.0, .5 * floater->opacity); + cairo_rectangle (context, + floater->path->start_point.x - 3, + floater->path->start_point.y - 3, + 6, 6); + cairo_fill (context); + cairo_set_source_rgba (context, 0.0, 0.5, 0.0, .5 * floater->opacity); + cairo_arc (context, + floater->path->start_control_point.x, + floater->path->start_control_point.y, + 3, 0.0, 2.0 * G_PI); + cairo_stroke (context); + cairo_set_source_rgba (context, 0.5, 0.0, 0.5, .5 * floater->opacity); + cairo_arc (context, + floater->path->end_control_point.x, + floater->path->end_control_point.y, + 3, 0.0, 2.0 * G_PI); + cairo_stroke (context); + cairo_set_source_rgba (context, 0.0, 0.0, 1.0, .5 * floater->opacity); + cairo_rectangle (context, + floater->path->end_point.x - 3, + floater->path->end_point.y - 3, + 6, 6); + cairo_fill (context); + + cairo_set_dash (context, dash_pattern, G_N_ELEMENTS (dash_pattern), 0); + cairo_set_source_rgba (context, .5, .5, .5, .2 * floater->scale); + cairo_move_to (context, floater->path->start_point.x, + floater->path->start_point.y); + cairo_line_to (context, floater->path->start_control_point.x, + floater->path->start_control_point.y); + cairo_stroke (context); + + cairo_move_to (context, floater->path->end_point.x, + floater->path->end_point.y); + cairo_line_to (context, floater->path->end_control_point.x, + floater->path->end_control_point.y); + cairo_stroke (context); + + cairo_restore (context); + } + + return TRUE; +} + +static ScreenSaver * +screen_saver_new (GtkDrawingArea *drawing_area, + const gchar *filename, + gint max_floater_count, + gboolean should_do_rotations, + gboolean should_show_paths) +{ + ScreenSaver *screen_saver; + + screen_saver = g_new (ScreenSaver, 1); + screen_saver->filename = g_strdup (filename); + screen_saver->drawing_area = GTK_WIDGET (drawing_area); + screen_saver->cached_sources = + g_hash_table_new_full (NULL, NULL, NULL, + (GDestroyNotify) cached_source_free); + + g_signal_connect_swapped (G_OBJECT (drawing_area), "size-allocate", + G_CALLBACK (screen_saver_on_size_allocate), + screen_saver); + + g_signal_connect_swapped (G_OBJECT (drawing_area), "expose-event", + G_CALLBACK (screen_saver_on_expose_event), + screen_saver); + + screen_saver->first_update_time = 0.0; + screen_saver->current_calculated_stats_time = 0.0; + screen_saver->last_calculated_stats_time = 0.0; + screen_saver->update_count = 0; + screen_saver->frame_count = 0; + screen_saver->updates_per_second = 0.0; + screen_saver->frames_per_second = 0.0; + screen_saver->floaters = NULL; + screen_saver->max_floater_count = max_floater_count; + + screen_saver->should_show_paths = should_show_paths; + screen_saver->should_do_rotations = should_do_rotations; + + screen_saver_get_initial_state (screen_saver); + + screen_saver->state_update_timeout_id = + g_timeout_add (1000 / (2.0 * OPTIMAL_FRAME_RATE), + (GSourceFunc) screen_saver_do_update_state, screen_saver); + + screen_saver->stats_update_timeout_id = + g_timeout_add (1000, (GSourceFunc) screen_saver_do_update_stats, + screen_saver); + + return screen_saver; +} + +static void +screen_saver_free (ScreenSaver *screen_saver) +{ + if (screen_saver == NULL) + return; + + g_free (screen_saver->filename); + + g_hash_table_destroy (screen_saver->cached_sources); + + if (screen_saver->state_update_timeout_id != 0) + g_source_remove (screen_saver->state_update_timeout_id); + + if (screen_saver->stats_update_timeout_id != 0) + g_source_remove (screen_saver->stats_update_timeout_id); + + screen_saver_destroy_floaters (screen_saver); + + g_free (screen_saver); +} + +static gdouble +screen_saver_get_timestamp (ScreenSaver *screen_saver) +{ + const gdouble microseconds_per_second = (gdouble ) G_USEC_PER_SEC; + gdouble timestamp; + GTimeVal now = { 0L, /* zero-filled */ }; + + g_get_current_time (&now); + timestamp = ((microseconds_per_second * now.tv_sec) + now.tv_usec) / + microseconds_per_second; + + return timestamp; +} + +static void +screen_saver_create_floaters (ScreenSaver *screen_saver) +{ + gint i; + + for (i = 0; i < screen_saver->max_floater_count; i++) + { + ScreenSaverFloater *floater; + Point position; + gdouble scale; + + position.x = g_random_double_range (screen_saver->canvas_rectangle.top_left_point.x, + screen_saver->canvas_rectangle.bottom_right_point.x); + position.y = g_random_double_range (screen_saver->canvas_rectangle.top_left_point.y, + screen_saver->canvas_rectangle.bottom_right_point.y); + + scale = g_random_double (); + + floater = screen_saver_floater_new (screen_saver, &position, scale); + + screen_saver->floaters = g_list_prepend (screen_saver->floaters, + floater); + } +} + +static gdouble +screen_saver_get_updates_per_second (ScreenSaver *screen_saver) +{ + return screen_saver->updates_per_second; +} + +static gdouble +screen_saver_get_frames_per_second (ScreenSaver *screen_saver) +{ + return screen_saver->frames_per_second; +} + +static gdouble +screen_saver_get_image_cache_usage (ScreenSaver *screen_saver) +{ + static const gdouble cache_capacity = (FLOATER_MAX_SIZE - FLOATER_MIN_SIZE + 1); + + return g_hash_table_size (screen_saver->cached_sources) / cache_capacity; +} + +static void +screen_saver_destroy_floaters (ScreenSaver *screen_saver) +{ + if (screen_saver->floaters == NULL) + return; + + g_list_foreach (screen_saver->floaters, (GFunc) screen_saver_floater_free, + NULL); + g_list_free (screen_saver->floaters); + + screen_saver->floaters = NULL; +} + +static void +screen_saver_on_size_allocate (ScreenSaver *screen_saver, + GtkAllocation *allocation) +{ + Rectangle canvas_rectangle; + + canvas_rectangle.top_left_point.x = allocation->x - .1 * allocation->width; + canvas_rectangle.top_left_point.y = allocation->y - .1 * allocation->height; + + canvas_rectangle.bottom_right_point.x = allocation->x + (1.1 * allocation->width); + canvas_rectangle.bottom_right_point.y = allocation->y + (1.1 * allocation->height); + + screen_saver->canvas_rectangle = canvas_rectangle; +} + +static gint +compare_floaters (ScreenSaverFloater *a, + ScreenSaverFloater *b) +{ + if (a->scale > b->scale) + return 1; + else if (abs (a->scale - b->scale) <= G_MINDOUBLE) + return 0; + else + return -1; +} + +static void +screen_saver_on_expose_event (ScreenSaver *screen_saver, + GdkEventExpose *event) +{ + GList *tmp; + cairo_t *context; + + if (screen_saver->floaters == NULL) + screen_saver_create_floaters (screen_saver); + + context = gdk_cairo_create (screen_saver->drawing_area->window); + + cairo_rectangle (context, + (double) event->area.x, + (double) event->area.y, + (double) event->area.width, + (double) event->area.height); + cairo_clip (context); + + screen_saver->floaters = g_list_sort (screen_saver->floaters, + (GCompareFunc)compare_floaters); + + for (tmp = screen_saver->floaters; tmp != NULL; tmp = tmp->next) + { + ScreenSaverFloater *floater; + GdkRectangle rect; + gint size; + + floater = (ScreenSaverFloater *) tmp->data; + + size = CLAMP ((int) (FLOATER_MAX_SIZE * floater->scale), + FLOATER_MIN_SIZE, FLOATER_MAX_SIZE); + + rect.x = (int) (floater->position.x - .5 * G_SQRT2 * size); + rect.y = (int) (floater->position.y - .5 * G_SQRT2 * size); + rect.width = G_SQRT2 * size; + rect.height = G_SQRT2 * size; + + if (!gdk_region_rect_in (event->region, &rect)) + continue; + + if (!screen_saver_floater_do_draw (screen_saver, floater, context)) + { + gtk_main_quit (); + break; + } + } + + cairo_destroy (context); + + screen_saver->draw_ops_pending = TRUE; + screen_saver->frame_count++; +} + +static void +screen_saver_update_state (ScreenSaver *screen_saver, + gdouble time) +{ + GList *tmp; + + tmp = screen_saver->floaters; + while (tmp != NULL) + { + ScreenSaverFloater *floater; + floater = (ScreenSaverFloater *) tmp->data; + + screen_saver_floater_update_state (screen_saver, floater, time); + + if (GTK_WIDGET_REALIZED (screen_saver->drawing_area) + && (floater->bounds.width > 0) && (floater->bounds.height > 0)) + { + gint size; + size = CLAMP ((int) (FLOATER_MAX_SIZE * floater->scale), + FLOATER_MIN_SIZE, FLOATER_MAX_SIZE); + + gtk_widget_queue_draw_area (screen_saver->drawing_area, + floater->bounds.x, + floater->bounds.y, + floater->bounds.width, + floater->bounds.height); + + /* the edges could concievably be spread across two + * pixels so we add +2 to invalidated region + */ + if (screen_saver->should_do_rotations) + gtk_widget_queue_draw_area (screen_saver->drawing_area, + (int) (floater->position.x - + .5 * G_SQRT2 * size), + (int) (floater->position.y - + .5 * G_SQRT2 * size), + G_SQRT2 * size + 2, + G_SQRT2 * size + 2); + else + gtk_widget_queue_draw_area (screen_saver->drawing_area, + (int) (floater->position.x - + .5 * size), + (int) (floater->position.y - + .5 * size), + size + 2, size + 2); + + if (screen_saver->should_show_paths) + gtk_widget_queue_draw (screen_saver->drawing_area); + } + + tmp = tmp->next; + } +} + +static void +screen_saver_get_initial_state (ScreenSaver *screen_saver) +{ + screen_saver->first_update_time = screen_saver_get_timestamp (screen_saver); + screen_saver_update_state (screen_saver, 0.0); +} + +static gboolean +screen_saver_do_update_state (ScreenSaver *screen_saver) +{ + gdouble current_update_time; + + /* flush pending requests to the X server and block for + * replies before proceeding to help prevent the X server from + * getting overrun with requests + */ + if (screen_saver->draw_ops_pending) + { + gdk_flush (); + screen_saver->draw_ops_pending = FALSE; + } + + current_update_time = screen_saver_get_timestamp (screen_saver); + screen_saver_update_state (screen_saver, current_update_time - + screen_saver->first_update_time); + screen_saver->update_count++; + return TRUE; +} + +static gboolean +screen_saver_do_update_stats (ScreenSaver *screen_saver) +{ + gdouble last_calculated_stats_time, seconds_since_last_stats_update; + + last_calculated_stats_time = screen_saver->current_calculated_stats_time; + screen_saver->current_calculated_stats_time = + screen_saver_get_timestamp (screen_saver); + screen_saver->last_calculated_stats_time = last_calculated_stats_time; + + if (abs (last_calculated_stats_time) <= G_MINDOUBLE) + return TRUE; + + seconds_since_last_stats_update = + screen_saver->current_calculated_stats_time - last_calculated_stats_time; + + screen_saver->updates_per_second = + screen_saver->update_count / seconds_since_last_stats_update; + screen_saver->frames_per_second = + screen_saver->frame_count / seconds_since_last_stats_update; + + screen_saver->update_count = 0; + screen_saver->frame_count = 0; + + return TRUE; +} + +static gboolean +do_print_screen_saver_stats (ScreenSaver *screen_saver) +{ + + g_print ("updates per second: %.2f, frames per second: %.2f, " + "image cache %.0f%% full\n", + screen_saver_get_updates_per_second (screen_saver), + screen_saver_get_frames_per_second (screen_saver), + screen_saver_get_image_cache_usage (screen_saver) * 100.0); + + return TRUE; +} + +int +main (int argc, + char *argv[]) +{ + ScreenSaver *screen_saver; + GtkWidget *window; + GtkWidget *drawing_area; + + GtkStateType state; + + GError *error; + + error = NULL; + + bindtextdomain (GETTEXT_PACKAGE, MATELOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + gtk_init_with_args (&argc, &argv, + /* translators: the word "image" here + * represents a command line argument + */ + _("image - floats images around the screen"), + options, GETTEXT_PACKAGE, &error); + + + if (error != NULL) + { + g_printerr (_("%s. See --help for usage information.\n"), + _(error->message)); + g_error_free (error); + return EX_SOFTWARE; + } + + if ((filenames == NULL) || (filenames[0] == NULL) || + (filenames[1] != NULL)) + { + g_printerr (_("You must specify one image. See --help for usage " + "information.\n")); + return EX_USAGE; + } + + window = gs_theme_window_new (); + + g_signal_connect (G_OBJECT (window), "delete-event", + G_CALLBACK (gtk_main_quit), NULL); + + drawing_area = gtk_drawing_area_new (); + + state = (GtkStateType) 0; + while (state < (GtkStateType) G_N_ELEMENTS (drawing_area->style->bg)) + { + gtk_widget_modify_bg (drawing_area, state, &drawing_area->style->mid[state]); + state++; + } + + gtk_widget_show (drawing_area); + gtk_container_add (GTK_CONTAINER (window), drawing_area); + + screen_saver = screen_saver_new (GTK_DRAWING_AREA (drawing_area), + filenames[0], max_floater_count, + should_do_rotations, should_show_paths); + g_strfreev (filenames); + + if (should_print_stats) + g_timeout_add (STAT_PRINT_FREQUENCY, + (GSourceFunc) do_print_screen_saver_stats, + screen_saver); + + if ((geometry == NULL) + || !gtk_window_parse_geometry (GTK_WINDOW (window), geometry)) + gtk_window_set_default_size (GTK_WINDOW (window), 640, 480); + + gtk_widget_show (window); + + gtk_main (); + + screen_saver_free (screen_saver); + + return EX_OK; +} |