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