ftp://ftp.krokus.ru/pub/OpenBSD/distfiles/xscreensaver-5.01.tar.gz
[xscreensaver] / hacks / glx / carousel.c
1 /* carousel, Copyright (c) 2005-2006 Jamie Zawinski <jwz@jwz.org>
2  * Loads a sequence of images and rotates them around.
3  *
4  * Permission to use, copy, modify, distribute, and sell this software and its
5  * documentation for any purpose is hereby granted without fee, provided that
6  * the above copyright notice appear in all copies and that both that
7  * copyright notice and this permission notice appear in supporting
8  * documentation.  No representations are made about the suitability of this
9  * software for any purpose.  It is provided "as is" without express or
10  * implied warranty.
11  *
12  * Created: 21-Feb-2005
13  */
14
15 #define DEF_FONT "-*-times-bold-r-normal-*-240-*"
16 #define DEFAULTS  "*count:           7         \n" \
17                   "*delay:           10000     \n" \
18                   "*wireframe:       False     \n" \
19                   "*showFPS:         False     \n" \
20                   "*fpsSolid:        True      \n" \
21                   "*useSHM:          True      \n" \
22                   "*font:          " DEF_FONT "\n" \
23                   "*desktopGrabber:  xscreensaver-getimage -no-desktop %s\n" \
24                   "*grabDesktopImages:   False \n" \
25                   "*chooseRandomImages:  True  \n"
26
27 # define refresh_carousel 0
28 # define release_carousel 0
29 # include "xlockmore.h"
30
31 #undef countof
32 #define countof(x) (sizeof((x))/sizeof((*x)))
33
34 #ifdef USE_GL
35
36 # define DEF_SPEED          "1.0"
37 # define DEF_DURATION       "20"
38 # define DEF_TITLES         "True"
39 # define DEF_ZOOM           "True"
40 # define DEF_TILT           "XY"
41 # define DEF_MIPMAP         "True"
42 # define DEF_DEBUG          "False"
43
44 #include "rotator.h"
45 #include "gltrackball.h"
46 #include "grab-ximage.h"
47 #include "texfont.h"
48
49 # ifndef HAVE_COCOA
50 #  include <X11/Intrinsic.h>     /* for XrmDatabase in -debug mode */
51 # endif
52
53 typedef struct {
54   double x, y, w, h;
55 } rect;
56
57 typedef enum { EARLY, NORMAL, LOADING, OUT, IN, DEAD } fade_mode;
58 static int fade_ticks = 60;
59
60 typedef struct {
61   char *title;                  /* the filename of this image */
62   int w, h;                     /* size in pixels of the image */
63   int tw, th;                   /* size in pixels of the texture */
64   XRectangle geom;              /* where in the image the bits are */
65   GLuint texid;
66 } image;
67
68 typedef struct {
69   ModeInfo *mi;
70   image current, loading;
71   GLfloat r, theta;             /* radius and rotation on the tube */
72   rotator *rot;                 /* for zoomery */
73   Bool from_top_p;              /* whether this image drops in or rises up */
74   time_t expires;               /* when this image should be replaced */
75   fade_mode mode;               /* in/out animation state */
76   int mode_tick;
77   Bool loaded_p;                /* whether background load is done */
78 } image_frame;
79
80
81 typedef struct {
82   GLXContext *glx_context;
83   rotator *rot;
84   trackball_state *trackball;
85   Bool button_down_p;
86   time_t button_down_time;
87
88   int nframes;                  /* how many frames are loaded */
89   int frames_size;
90   image_frame **frames;         /* pointers to the frames */
91
92   Bool awaiting_first_images_p;
93   int loads_in_progress;
94
95   texture_font_data *texfont;
96
97   fade_mode mode;
98   int mode_tick;
99
100   int loading_sw, loading_sh;
101
102   time_t last_time, now;
103   int draw_tick;
104
105 } carousel_state;
106
107 static carousel_state *sss = NULL;
108
109
110 /* Command-line arguments
111  */
112 static GLfloat speed;       /* animation speed scale factor */
113 static int duration;        /* reload images after this long */
114 static Bool mipmap_p;       /* Use mipmaps instead of single textures. */
115 static Bool titles_p;       /* Display image titles. */
116 static Bool zoom_p;         /* Throb the images in and out as they spin. */
117 static char *tilt_str;
118 static Bool tilt_x_p;       /* Tilt axis towards the viewer */
119 static Bool tilt_y_p;       /* Tilt axis side to side */
120 static Bool debug_p;        /* Be loud and do weird things. */
121
122
123 static XrmOptionDescRec opts[] = {
124   {"-zoom",         ".zoom",          XrmoptionNoArg, "True"  },
125   {"-no-zoom",      ".zoom",          XrmoptionNoArg, "False" },
126   {"-tilt",         ".tilt",          XrmoptionSepArg, 0  },
127   {"-no-tilt",      ".tilt",          XrmoptionNoArg, ""  },
128   {"-titles",       ".titles",        XrmoptionNoArg, "True"  },
129   {"-no-titles",    ".titles",        XrmoptionNoArg, "False" },
130   {"-mipmaps",      ".mipmap",        XrmoptionNoArg, "True"  },
131   {"-no-mipmaps",   ".mipmap",        XrmoptionNoArg, "False" },
132   {"-duration",     ".duration",      XrmoptionSepArg, 0 },
133   {"-debug",        ".debug",         XrmoptionNoArg, "True"  },
134   {"-font",         ".font",          XrmoptionSepArg, 0 },
135   {"-speed",        ".speed",         XrmoptionSepArg, 0 },
136 };
137
138 static argtype vars[] = {
139   { &mipmap_p,      "mipmap",       "Mipmap",       DEF_MIPMAP,      t_Bool},
140   { &debug_p,       "debug",        "Debug",        DEF_DEBUG,       t_Bool},
141   { &titles_p,      "titles",       "Titles",       DEF_TITLES,      t_Bool},
142   { &zoom_p,        "zoom",         "Zoom",         DEF_ZOOM,        t_Bool},
143   { &tilt_str,      "tilt",         "Tilt",         DEF_TILT,        t_String},
144   { &speed,         "speed",        "Speed",        DEF_SPEED,       t_Float},
145   { &duration,      "duration",     "Duration",     DEF_DURATION,    t_Int},
146 };
147
148 ENTRYPOINT ModeSpecOpt carousel_opts = {countof(opts), opts, countof(vars), vars, NULL};
149
150
151 /* Allocates a frame structure and stores it in the list.
152  */
153 static image_frame *
154 alloc_frame (ModeInfo *mi)
155 {
156   carousel_state *ss = &sss[MI_SCREEN(mi)];
157   image_frame *frame = (image_frame *) calloc (1, sizeof (*frame));
158
159   frame->mi = mi;
160   frame->mode = EARLY;
161   frame->rot = make_rotator (0, 0, 0, 0, 0.04 * frand(1.0) * speed, False);
162
163   glGenTextures (1, &frame->current.texid);
164   glGenTextures (1, &frame->loading.texid);
165   if (frame->current.texid <= 0) abort();
166   if (frame->loading.texid <= 0) abort();
167
168   if (ss->frames_size <= ss->nframes)
169     {
170       ss->frames_size = (ss->frames_size * 1.2) + ss->nframes;
171       ss->frames = (image_frame **)
172         realloc (ss->frames, ss->frames_size * sizeof(*ss->frames));
173       if (! ss->frames)
174         {
175           fprintf (stderr, "%s: out of memory (%d images)\n",
176                    progname, ss->frames_size);
177           exit (1);
178         }
179     }
180
181   ss->frames[ss->nframes++] = frame;
182
183   return frame;
184 }
185
186
187 static void image_loaded_cb (const char *filename, XRectangle *geom,
188                              int image_width, int image_height,
189                              int texture_width, int texture_height,
190                              void *closure);
191
192
193 /* Load a new file into the given image struct.
194  */
195 static void
196 load_image (ModeInfo *mi, image_frame *frame)
197 {
198   carousel_state *ss = &sss[MI_SCREEN(mi)];
199   int wire = MI_IS_WIREFRAME(mi);
200
201   if (debug_p && !wire && frame->current.w != 0)
202     fprintf (stderr, "%s:  dropped %4d x %-4d  %4d x %-4d  \"%s\"\n",
203              progname, 
204              frame->current.geom.width, 
205              frame->current.geom.height, 
206              frame->current.tw, frame->current.th,
207              (frame->current.title ? frame->current.title : "(null)"));
208
209   switch (frame->mode) 
210     {
211     case EARLY:  break;
212     case NORMAL: frame->mode = LOADING; break;
213     default: abort();
214     }
215
216   ss->loads_in_progress++;
217
218   if (wire)
219     image_loaded_cb (0, 0, 0, 0, 0, 0, frame);
220   else
221     {
222       int w = (MI_WIDTH(mi)  / 2) - 1;
223       int h = (MI_HEIGHT(mi) / 2) - 1;
224       if (w <= 10) w = 10;
225       if (h <= 10) h = 10;
226       load_texture_async (mi->xgwa.screen, mi->window, *ss->glx_context, w, h,
227                           mipmap_p, frame->loading.texid, 
228                           image_loaded_cb, frame);
229     }
230 }
231
232
233 /* Callback that tells us that the texture has been loaded.
234  */
235 static void
236 image_loaded_cb (const char *filename, XRectangle *geom,
237                  int image_width, int image_height,
238                  int texture_width, int texture_height,
239                  void *closure)
240 {
241   image_frame *frame = (image_frame *) closure;
242   ModeInfo *mi = frame->mi;
243   carousel_state *ss = &sss[MI_SCREEN(mi)];
244   int wire = MI_IS_WIREFRAME(mi);
245
246   if (wire)
247     {
248       frame->loading.w = MI_WIDTH (mi) * (0.5 + frand (1.0));
249       frame->loading.h = MI_HEIGHT (mi);
250       frame->loading.geom.width  = frame->loading.w;
251       frame->loading.geom.height = frame->loading.h;
252       goto DONE;
253     }
254
255   if (image_width == 0 || image_height == 0)
256     exit (1);
257
258   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
259   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
260                    mipmap_p ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR);
261
262   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
263   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
264
265   frame->loading.w  = image_width;
266   frame->loading.h  = image_height;
267   frame->loading.tw = texture_width;
268   frame->loading.th = texture_height;
269   frame->loading.geom = *geom;
270
271   if (frame->loading.title)
272     free (frame->loading.title);
273   frame->loading.title = (filename ? strdup (filename) : 0);
274
275   if (frame->loading.title)   /* strip filename to part after last /. */
276     {
277       char *s = strrchr (frame->loading.title, '/');
278       if (s) strcpy (frame->loading.title, s+1);
279     }
280
281   if (debug_p)
282     fprintf (stderr, "%s:   loaded %4d x %-4d  %4d x %-4d  \"%s\"\n",
283              progname,
284              frame->loading.geom.width, 
285              frame->loading.geom.height, 
286              frame->loading.tw, frame->loading.th,
287              (frame->loading.title ? frame->loading.title : "(null)"));
288
289  DONE:
290
291   frame->loaded_p = True;
292
293   if (ss->loads_in_progress <= 0) abort();
294   ss->loads_in_progress--;
295
296   /* This image expires N seconds after it finished loading. */
297   frame->expires = time((time_t *) 0) + (duration * MI_COUNT(mi));
298
299   switch (frame->mode) 
300     {
301     case EARLY:         /* part of the initial batch of images */
302       {
303         image swap = frame->current;
304         frame->current = frame->loading;
305         frame->loading = swap;
306       }
307       break;
308     case LOADING:       /* start dropping the old image out */
309       {
310         frame->mode = OUT;
311         frame->mode_tick = fade_ticks / speed;
312         frame->from_top_p = random() & 1;
313       }
314       break;
315     default:
316       abort();
317     }
318 }
319
320
321 static void loading_msg (ModeInfo *mi, int n);
322
323 static Bool
324 load_initial_images (ModeInfo *mi)
325 {
326   carousel_state *ss = &sss[MI_SCREEN(mi)];
327   int i;
328   Bool all_loaded_p = True;
329   for (i = 0; i < ss->nframes; i++)
330     if (! ss->frames[i]->loaded_p)
331       all_loaded_p = False;
332
333   if (all_loaded_p)
334     {
335       if (ss->nframes < MI_COUNT (mi))
336         {
337           /* The frames currently on the list are fully loaded.
338              Start the next one loading.  (We run the image loader
339              asynchronously, but we load them one at a time.)
340            */
341           load_image (mi, alloc_frame (mi));
342         }
343       else
344         {
345           /* The first batch of images are now all loaded!
346              Stagger the expire times so that they don't all drop out at once.
347            */
348           time_t now = time((time_t *) 0);
349           int i;
350
351           for (i = 0; i < ss->nframes; i++)
352             {
353               image_frame *frame = ss->frames[i];
354               frame->r = 1.0;
355               frame->theta = i * 360.0 / ss->nframes;
356               frame->expires = now + (duration * (i + 1));
357               frame->mode = NORMAL;
358             }
359
360           /* Instead of always going clockwise, shuffle the expire times
361              of the frames so that they drop out in a random order.
362           */
363           for (i = 0; i < ss->nframes; i++)
364             {
365               image_frame *frame1 = ss->frames[i];
366               image_frame *frame2 = ss->frames[random() % ss->nframes];
367               time_t swap = frame1->expires;
368               frame1->expires = frame2->expires;
369               frame2->expires = swap;
370             }
371
372           ss->awaiting_first_images_p = False;
373         }
374     }
375       
376   loading_msg (mi, ss->nframes-1);
377
378   return !ss->awaiting_first_images_p;
379 }
380
381
382 ENTRYPOINT void
383 reshape_carousel (ModeInfo *mi, int width, int height)
384 {
385   GLfloat h = (GLfloat) height / (GLfloat) width;
386
387   glViewport (0, 0, (GLint) width, (GLint) height);
388
389   glMatrixMode(GL_PROJECTION);
390   glLoadIdentity();
391   gluPerspective (60.0, 1/h, 1.0, 8.0);
392
393   glMatrixMode(GL_MODELVIEW);
394   glLoadIdentity();
395   gluLookAt( 0.0, 0.0, 2.6,
396              0.0, 0.0, 0.0,
397              0.0, 1.0, 0.0);
398
399   glClear(GL_COLOR_BUFFER_BIT);
400 }
401
402
403 ENTRYPOINT Bool
404 carousel_handle_event (ModeInfo *mi, XEvent *event)
405 {
406   carousel_state *ss = &sss[MI_SCREEN(mi)];
407
408   if (event->xany.type == ButtonPress &&
409       event->xbutton.button == Button1)
410     {
411       if (! ss->button_down_p)
412         ss->button_down_time = time((time_t *) 0);
413       ss->button_down_p = True;
414       gltrackball_start (ss->trackball,
415                          event->xbutton.x, event->xbutton.y,
416                          MI_WIDTH (mi), MI_HEIGHT (mi));
417       return True;
418     }
419   else if (event->xany.type == ButtonRelease &&
420            event->xbutton.button == Button1)
421     {
422       if (ss->button_down_p)
423         {
424           /* Add the time the mouse was held to the expire times of all
425              frames, so that mouse-dragging doesn't count against
426              image expiration.
427            */
428           int secs = time((time_t *) 0) - ss->button_down_time;
429           int i;
430           for (i = 0; i < ss->nframes; i++)
431             ss->frames[i]->expires += secs;
432         }
433       ss->button_down_p = False;
434       return True;
435     }
436   else if (event->xany.type == ButtonPress &&
437            (event->xbutton.button == Button4 ||
438             event->xbutton.button == Button5))
439     {
440       gltrackball_mousewheel (ss->trackball, event->xbutton.button, 5,
441                               !event->xbutton.state);
442       return True;
443     }
444   else if (event->xany.type == MotionNotify &&
445            ss->button_down_p)
446     {
447       gltrackball_track (ss->trackball,
448                          event->xmotion.x, event->xmotion.y,
449                          MI_WIDTH (mi), MI_HEIGHT (mi));
450       return True;
451     }
452
453   return False;
454 }
455
456
457 /* Kludge to add "-v" to invocation of "xscreensaver-getimage" in -debug mode
458  */
459 static void
460 hack_resources (Display *dpy)
461 {
462 # ifndef HAVE_COCOA
463   char *res = "desktopGrabber";
464   char *val = get_string_resource (dpy, res, "DesktopGrabber");
465   char buf1[255];
466   char buf2[255];
467   XrmValue value;
468   XrmDatabase db = XtDatabase (dpy);
469   sprintf (buf1, "%.100s.%.100s", progname, res);
470   sprintf (buf2, "%.200s -v", val);
471   value.addr = buf2;
472   value.size = strlen(buf2);
473   XrmPutResource (&db, buf1, "String", &value);
474 # endif /* !HAVE_COCOA */
475 }
476
477
478 static void
479 loading_msg (ModeInfo *mi, int n)
480 {
481   carousel_state *ss = &sss[MI_SCREEN(mi)];
482   int wire = MI_IS_WIREFRAME(mi);
483   char text[100];
484   GLfloat scale;
485
486   if (wire) return;
487
488   if (n == 0)
489     sprintf (text, "Loading images...");
490   else
491     sprintf (text, "Loading images...  (%d%%)",
492              (int) (n * 100 / MI_COUNT(mi)));
493
494   if (ss->loading_sw == 0)    /* only do this once, so that the string doesn't move. */
495     ss->loading_sw = texture_string_width (ss->texfont, text, &ss->loading_sh);
496
497   scale = ss->loading_sh / (GLfloat) MI_HEIGHT(mi);
498
499   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
500
501   glMatrixMode(GL_PROJECTION);
502   glPushMatrix();
503   glLoadIdentity();
504
505   glMatrixMode(GL_MODELVIEW);
506   glPushMatrix();
507   glLoadIdentity();
508   gluOrtho2D(0, MI_WIDTH(mi), 0, MI_HEIGHT(mi));
509
510   glTranslatef ((MI_WIDTH(mi)  - ss->loading_sw) / 2,
511                 (MI_HEIGHT(mi) - ss->loading_sh) / 2,
512                 0);
513   glColor3f (1, 1, 0);
514   glEnable (GL_TEXTURE_2D);
515   glDisable (GL_DEPTH_TEST);
516   print_texture_string (ss->texfont, text);
517   glEnable (GL_DEPTH_TEST);
518   glPopMatrix();
519
520   glMatrixMode(GL_PROJECTION);
521   glPopMatrix();
522
523   glMatrixMode(GL_MODELVIEW);
524
525   glFinish();
526   glXSwapBuffers (MI_DISPLAY (mi), MI_WINDOW(mi));
527 }
528
529
530 ENTRYPOINT void
531 init_carousel (ModeInfo *mi)
532 {
533   int screen = MI_SCREEN(mi);
534   carousel_state *ss;
535   int wire = MI_IS_WIREFRAME(mi);
536   
537   if (sss == NULL) {
538     if ((sss = (carousel_state *)
539          calloc (MI_NUM_SCREENS(mi), sizeof(carousel_state))) == NULL)
540       return;
541   }
542   ss = &sss[screen];
543
544   if ((ss->glx_context = init_GL(mi)) != NULL) {
545     reshape_carousel (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
546   } else {
547     MI_CLEARWINDOW(mi);
548   }
549
550   if (!tilt_str || !*tilt_str)
551     ;
552   else if (!strcasecmp (tilt_str, "0"))
553     ;
554   else if (!strcasecmp (tilt_str, "X"))
555     tilt_x_p = 1;
556   else if (!strcasecmp (tilt_str, "Y"))
557     tilt_y_p = 1;
558   else if (!strcasecmp (tilt_str, "XY"))
559     tilt_x_p = tilt_y_p = 1;
560   else
561     {
562       fprintf (stderr, "%s: tilt must be 'X', 'Y', 'XY' or '', not '%s'\n",
563                progname, tilt_str);
564       exit (1);
565     }
566
567   {
568     double spin_speed   = speed * 0.2;    /* rotation of tube around axis */
569     double spin_accel   = speed * 0.1;
570     double wander_speed = speed * 0.001;  /* tilting of axis */
571
572     spin_speed   *= 0.9 + frand(0.2);
573     wander_speed *= 0.9 + frand(0.2);
574
575     ss->rot = make_rotator (spin_speed, spin_speed, spin_speed,
576                             spin_accel, wander_speed, True);
577
578     ss->trackball = gltrackball_init ();
579   }
580
581   glDisable (GL_LIGHTING);
582   glEnable (GL_DEPTH_TEST);
583   glDisable (GL_CULL_FACE);
584
585   if (! wire)
586     {
587       glShadeModel (GL_SMOOTH);
588       glEnable (GL_LINE_SMOOTH);
589       /* This gives us a transparent diagonal slice through each image! */
590       /* glEnable (GL_POLYGON_SMOOTH); */
591       glHint (GL_LINE_SMOOTH_HINT, GL_NICEST);
592       glEnable (GL_BLEND);
593       glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
594       glEnable (GL_ALPHA_TEST);
595
596       glEnable (GL_POLYGON_OFFSET_FILL);
597       glPolygonOffset (1.0, 1.0);
598
599     }
600
601   ss->texfont = load_texture_font (MI_DISPLAY(mi), "font");
602
603   if (debug_p)
604     hack_resources (MI_DISPLAY (mi));
605
606   ss->nframes = 0;
607   ss->frames_size = 10;
608   ss->frames = (image_frame **)
609     calloc (1, ss->frames_size * sizeof(*ss->frames));
610
611   ss->mode = IN;
612   ss->mode_tick = fade_ticks / speed;
613
614   ss->awaiting_first_images_p = True;
615 }
616
617
618 static void
619 draw_frame (ModeInfo *mi, image_frame *frame, time_t now, Bool body_p)
620 {
621   carousel_state *ss = &sss[MI_SCREEN(mi)];
622   int wire = MI_IS_WIREFRAME(mi);
623
624   GLfloat texw  = frame->current.geom.width  / (GLfloat) frame->current.tw;
625   GLfloat texh  = frame->current.geom.height / (GLfloat) frame->current.th;
626   GLfloat texx1 = frame->current.geom.x / (GLfloat) frame->current.tw;
627   GLfloat texy1 = frame->current.geom.y / (GLfloat) frame->current.th;
628   GLfloat texx2 = texx1 + texw;
629   GLfloat texy2 = texy1 + texh;
630   GLfloat aspect = ((GLfloat) frame->current.geom.height /
631                     (GLfloat) frame->current.geom.width);
632
633   glBindTexture (GL_TEXTURE_2D, frame->current.texid);
634
635   glPushMatrix();
636
637   /* Position this image on the wheel.
638    */
639   glRotatef (frame->theta, 0, 1, 0);
640   glTranslatef (0, 0, frame->r);
641
642   /* Scale down the image so that all N frames fit on the wheel
643      without bumping in to each other.
644   */
645   {
646     GLfloat t, s;
647     switch (ss->nframes)
648       {
649       case 1:  t = -1.0; s = 1.7; break;
650       case 2:  t = -0.8; s = 1.6; break;
651       case 3:  t = -0.4; s = 1.5; break;
652       case 4:  t = -0.2; s = 1.3; break;
653       default: t =  0.0; s = 6.0 / ss->nframes; break;
654       }
655     glTranslatef (0, 0, t);
656     glScalef (s, s, s);
657   }
658
659   /* Center this image on the wheel plane.
660    */
661   glTranslatef (-0.5, -(aspect/2), 0);
662
663   /* Move as per the "zoom in and out" setting.
664    */
665   if (zoom_p)
666     {
667       double x, y, z;
668       /* Only use the Z component of the rotator for in/out position. */
669       get_position (frame->rot, &x, &y, &z, !ss->button_down_p);
670       glTranslatef (0, 0, z/2);
671     }
672
673   /* Compute the "drop in and out" state.
674    */
675   switch (frame->mode)
676     {
677     case EARLY:
678       abort();
679       break;
680     case NORMAL:
681       if (!ss->button_down_p &&
682           now >= frame->expires &&
683           ss->loads_in_progress == 0)  /* only load one at a time */
684         load_image (mi, frame);
685       break;
686     case LOADING:
687       break;
688     case OUT:
689       if (--frame->mode_tick <= 0) {
690         image swap = frame->current;
691         frame->current = frame->loading;
692         frame->loading = swap;
693
694         frame->mode = IN;
695         frame->mode_tick = fade_ticks / speed;
696       }
697       break;
698     case IN:
699       if (--frame->mode_tick <= 0)
700         frame->mode = NORMAL;
701       break;
702     default:
703       abort();
704     }
705
706   /* Now translate for current in/out state.
707    */
708   if (frame->mode == OUT || frame->mode == IN)
709     {
710       GLfloat t = (frame->mode == OUT
711                    ? frame->mode_tick / (fade_ticks / speed)
712                    : (((fade_ticks / speed) - frame->mode_tick + 1) /
713                       (fade_ticks / speed)));
714       t = 5 * (1 - t);
715       if (frame->from_top_p) t = -t;
716       glTranslatef (0, t, 0);
717     }
718
719   if (body_p)                                   /* Draw the image quad. */
720     {
721       if (! wire)
722         {
723           glColor3f (1, 1, 1);
724           glNormal3f (0, 0, 1);
725           glEnable (GL_TEXTURE_2D);
726           glBegin (GL_QUADS);
727           glNormal3f (0, 0, 1);
728           glTexCoord2f (texx1, texy2); glVertex3f (0, 0, 0);
729           glTexCoord2f (texx2, texy2); glVertex3f (1, 0, 0);
730           glTexCoord2f (texx2, texy1); glVertex3f (1, aspect, 0);
731           glTexCoord2f (texx1, texy1); glVertex3f (0, aspect, 0);
732           glEnd();
733         }
734
735       /* Draw a box around it.
736        */
737       glLineWidth (2.0);
738       glColor3f (0.5, 0.5, 0.5);
739       glDisable (GL_TEXTURE_2D);
740       glBegin (GL_LINE_LOOP);
741       glVertex3f (0, 0, 0);
742       glVertex3f (1, 0, 0);
743       glVertex3f (1, aspect, 0);
744       glVertex3f (0, aspect, 0);
745       glEnd();
746
747     }
748   else                                  /* Draw a title under the image. */
749     {
750       int sw, sh;
751       GLfloat scale = 0.05;
752       char *title = frame->current.title ? frame->current.title : "(untitled)";
753       sw = texture_string_width (ss->texfont, title, &sh);
754
755       glTranslatef (0, -scale, 0);
756
757       scale /= sh;
758       glScalef (scale, scale, scale);
759
760       glTranslatef (((1/scale) - sw) / 2, 0, 0);
761       glColor3f (1, 1, 1);
762
763       if (!wire)
764         {
765           glEnable (GL_TEXTURE_2D);
766           print_texture_string (ss->texfont, title);
767         }
768       else
769         {
770           glBegin (GL_LINE_LOOP);
771           glVertex3f (0,  0,  0);
772           glVertex3f (sw, 0,  0);
773           glVertex3f (sw, sh, 0);
774           glVertex3f (0,  sh, 0);
775           glEnd();
776         }
777     }
778
779   glPopMatrix();
780 }
781
782
783 ENTRYPOINT void
784 draw_carousel (ModeInfo *mi)
785 {
786   carousel_state *ss = &sss[MI_SCREEN(mi)];
787   int i;
788
789   if (!ss->glx_context)
790     return;
791
792   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(ss->glx_context));
793
794   if (ss->awaiting_first_images_p)
795     if (!load_initial_images (mi))
796       return;
797
798   /* Only check the wall clock every 10 frames */
799   {
800     if (ss->now == 0 || ss->draw_tick++ > 10)
801       {
802         ss->now = time((time_t *) 0);
803         if (ss->last_time == 0) ss->last_time = ss->now;
804         ss->draw_tick = 0;
805       }
806   }
807
808
809   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
810
811   glPushMatrix();
812
813
814   /* Run the startup "un-shrink" animation.
815    */
816   switch (ss->mode)
817     {
818     case IN:
819       if (--ss->mode_tick <= 0)
820         {
821           ss->mode = NORMAL;
822           ss->last_time = time((time_t *) 0);
823         }
824       break;
825     case NORMAL:
826       break;
827     default:
828       abort();
829     }
830
831
832   /* Scale as per the startup "un-shrink" animation.
833    */
834   if (ss->mode != NORMAL)
835     {
836       GLfloat s = (ss->mode == OUT
837                    ? ss->mode_tick / (fade_ticks / speed)
838                    : (((fade_ticks / speed) - ss->mode_tick + 1) /
839                       (fade_ticks / speed)));
840       glScalef (s, s, s);
841     }
842
843   /* Rotate and tilt as per the user, and the motion modeller.
844    */
845   {
846     double x, y, z;
847     gltrackball_rotate (ss->trackball);
848
849     /* Tilt the tube up or down by up to 30 degrees */
850     get_position (ss->rot, &x, &y, &z, !ss->button_down_p);
851     if (tilt_x_p)
852       glRotatef (15 - (x * 30), 1, 0, 0);
853     if (tilt_y_p)
854       glRotatef (7  - (y * 14), 0, 0, 1);
855
856     /* Only use the Y component of the rotator. */
857     get_rotation (ss->rot, &x, &y, &z, !ss->button_down_p);
858     glRotatef (y * 360, 0, 1, 0);
859   }
860
861   /* First draw each image, then draw the titles.  GL insists that you
862      draw back-to-front in order to make alpha blending work properly,
863      so we need to draw all of the 100% opaque images before drawing
864      any of the not-100%-opaque titles.
865    */
866   for (i = 0; i < ss->nframes; i++)
867     draw_frame (mi, ss->frames[i], ss->now, True);
868   if (titles_p)
869     for (i = 0; i < ss->nframes; i++)
870       draw_frame (mi, ss->frames[i], ss->now, False);
871
872   glPopMatrix();
873
874   if (mi->fps_p) do_fps (mi);
875   glFinish();
876   glXSwapBuffers (MI_DISPLAY (mi), MI_WINDOW(mi));
877 }
878
879 XSCREENSAVER_MODULE ("Carousel", carousel)
880
881 #endif /* USE_GL */