From http://www.jwz.org/xscreensaver/xscreensaver-5.38.tar.gz
[xscreensaver] / hacks / glx / esper.c
1 /* esper, Copyright (c) 2017 Jamie Zawinski <jwz@jwz.org>
2  * Enhance 224 to 176. Pull out track right. Center in pull back.
3  * Pull back. Wait a minute. Go right. Stop. Enhance 57 19. Track 45 left.
4  * Gimme a hardcopy right there.
5  *
6  * Permission to use, copy, modify, distribute, and sell this software and its
7  * documentation for any purpose is hereby granted without fee, provided that
8  * the above copyright notice appear in all copies and that both that
9  * copyright notice and this permission notice appear in supporting
10  * documentation.  No representations are made about the suitability of this
11  * software for any purpose.  It is provided "as is" without express or
12  * implied warranty.
13  */
14
15 /*
16   The Esper machine has a 4:3 display, about 12" diagonal.
17   The display is overlayed with a 10x7 grid of blue lines.
18   The scene goes approximately like this:
19
20       "Enhance 224 To 176."
21
22           ZM 0000  NS 0000  EW 0000
23
24       The reticle is displayed centered.
25       It moves in 8 steps with 3 frame blur to move to around to grid 1,4.
26
27           ZM 0000  NS 0000  EW 0000
28           ZM 0000  NS 0001  EW 0001
29           ZM 0000  NS 0001  EW 0002
30           ZM 0000  NS 0002  EW 0003
31           ZM 0000  NS 0003  EW 0005
32           ZM 0000  NS 0004  EW 0008
33           ZM 0000  NS 0015  EW 0011
34
35       These numbers appear to have little relation to what we are
36       actually seeing on the screen.  Also the same numbers are
37       repeated later when looking at totally different parts of
38       the photograph.
39
40           ZM 0000  NS 0117  EW 0334
41
42       The box appears: 8 steps, final box is 1.5x2.25 at -0.5,4.0.
43
44           ZM 4086  NS 0117  EW 0334
45
46       The box blinks yellow 5x.
47       The image's zoom-and-pan takes 8 steps, with no text on the screen.
48       The zoom is in discreet steps, with flashes.
49       The grid stays the same size the whole time.
50       The flashes look like solarization to blue.
51       When the zoom is finished, there is still no text.
52
53       "Enhance."  Goes 4 more ticks down the same hole?
54       "Stop."  Moves up a little bit at the end.
55
56       Then with no instructions, it goes 20 ticks by itself, off camera.
57
58       "Move in."  10 ticks.
59       "Stop."  (We are looking at a fist in the picture.)
60       "Pull out track right."
61       "Stop."  (We are looking at a newspaper.)
62       "Center and pull back."
63       "Stop."  (We just passed the round mirror.)
64       "Track 45 right."
65       "Stop."
66       "Center and stop."
67
68       This time there was no grid until it stopped, then the grid showed up.
69       There is video tearing at the bottom.
70
71       "Enhance 34 to 36."
72
73           ZM 0000  NS 0063  EW 0185
74           ZM 0000  NS 0197  EW 0334
75           ZM 3841  NS 0197  EW 0334
76
77       It kind of zooms in to the center wobbly and willy-nilly.
78       We are now looking at a glass.
79
80       "Pan right and pull back."  (There is no grid while moving again.)
81       "Stop."
82
83       Ok, at this point, we enter fantasy-land.  From here on, the images
84       shown are very high resolution with no noise.  And suddenly the 
85       UI on the Esper is *way* higher resolution.  My theory is that from
86       this point on in the scene, we are not looking at the literal Esper
87       machine, but instead the movie is presenting Decard's perception of
88       it.  We're seeing the room, not the photo of the room.  The map has
89       become the territory. 
90
91       "Enhance 34 to 46."
92
93           ZM 0000  NS 0197  EW 0334
94
95       This has the reticle and box only, no grid, ends with no grid.
96
97       "Pull back."
98       "Wait a minute. Go right."
99       "Stop."
100       Now it's going around the corner or something.
101
102       "Enhance 57 19."
103       This has a reticle then box, but the image started zooming early.
104
105       "Track 45 left."
106       zooms out and moves left
107
108       "Stop."  (O hai Zhora.)
109       "Enhance 15 to 23."
110
111           ZM 3852  NS 0197  EW 0334
112
113       "Gimme a hardcopy right there."
114
115       The printer polaroid is WAY lower resolution than the image we see on
116       the "screen" -- in keeping with my theory that we were not seeing the
117       screen.
118
119
120   TODO:
121
122   * There's a glitch at the top/bottom of the texfont textures.
123   * "Pull back" isn't quite symmetric: zoom origin is slightly off.
124   * Maybe display text like "Pull right" and "Stop".
125 */
126
127
128 /* Use a small point size to keep it nice and grainy. */
129 #if defined(HAVE_COCOA) || defined(HAVE_ANDROID)
130 # define TITLE_FONT "OCR A Std 10, Lucida Console 10, Monaco 10"
131 #else  /* real X11 */
132 # define TITLE_FONT "-*-courier-bold-r-*-*-*-100-*-*-m-*-*-*"
133 #endif
134
135 #define DEFAULTS  "*delay:           20000                \n" \
136                   "*wireframe:       False                \n" \
137                   "*showFPS:         False                \n" \
138                   "*fpsTop:          True                 \n" \
139                   "*useSHM:          True                 \n" \
140                   "*titleFont: "     TITLE_FONT          "\n" \
141                   "*desktopGrabber:  xscreensaver-getimage -no-desktop %s\n" \
142                   "*grabDesktopImages:   False \n" \
143                   "*chooseRandomImages:  True  \n" \
144                   "*gridColor:    #4444FF\n" \
145                   "*reticleColor: #FFFF77\n" \
146                   "*textColor:    #FFFFBB\n" \
147
148 # define free_esper 0
149 # define refresh_esper 0
150 # define release_esper 0
151 # include "xlockmore.h"
152
153 #undef countof
154 #define countof(x) (sizeof((x))/sizeof((*x)))
155
156 #undef RANDSIGN
157 #define RANDSIGN() ((random() & 1) ? 1 : -1)
158 #undef BELLRAND
159 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
160
161 #ifdef USE_GL
162
163 #undef SMOOTH
164
165 # define DEF_GRID_SIZE      "11"
166 # define DEF_GRID_THICKNESS "15"
167 # define DEF_TITLES         "True"
168 # define DEF_SPEED          "1.0"
169 # define DEF_DEBUG          "False"
170
171 #include "grab-ximage.h"
172 #include "texfont.h"
173
174 #ifdef HAVE_XSHM_EXTENSION
175 # include "xshm.h"  /* to get <sys/shm.h> */
176 #endif
177
178
179 typedef struct {
180   double x, y, w, h;
181 } rect;
182
183 typedef struct {
184   ModeInfo *mi;
185   unsigned long id;                /* unique */
186   char *title;                     /* the filename of this image */
187   int w, h;                        /* size in pixels of the image */
188   int tw, th;                      /* size in pixels of the texture */
189   XRectangle geom;                 /* where in the image the bits are */
190   Bool loaded_p;                   /* whether the image has finished loading */
191   Bool used_p;                     /* whether the image has yet appeared
192                                       on screen */
193   GLuint texid;                    /* which texture contains the image */
194   int refcount;                    /* how many sprites refer to this image */
195 } image;
196
197
198 typedef enum {
199   BLANK,
200   GRID_ON,
201   IMAGE_LOAD,
202   IMAGE_UNLOAD,
203   IMAGE_FORCE_UNLOAD,
204   REPOSITION,
205   RETICLE_ON,
206   RETICLE_MOVE,
207   BOX_MOVE,
208   IMAGE_ZOOM,
209   MANUAL_RETICLE_ON,
210   MANUAL_RETICLE,
211   MANUAL_BOX_ON,
212   MANUAL_BOX,
213 } anim_state;
214
215 typedef enum { NEW, IN, FULL, OUT, DEAD } sprite_state;
216 typedef enum { IMAGE, RETICLE, BOX, GRID, FLASH, TEXT } sprite_type;
217
218 typedef struct {
219   unsigned long id;                /* unique */
220   sprite_type type;
221   image *img;                      /* type = IMAGE */
222   unsigned long text_id;           /* type = TEXT */
223   char *text;
224   GLfloat opacity;
225   GLfloat thickness_scale;         /* line and image types */
226   Bool throb_p;
227   double start_time;               /* when this animation began */
228   double duration;                 /* lifetime of sprite in seconds; 0 = inf */
229   double fade_duration;            /* speed of fade in and fade out */
230   double pause_duration;           /* delay before fade-in starts */
231   Bool remain_p;                   /* pause forever before fade-out */
232   rect from, to, current;          /* the journey this image is taking */
233   sprite_state state;              /* the state we're in right now */
234   double state_time;               /* time of last state change */
235   int frame_count;                 /* frames since last state change */
236   Bool fatbits_p;                  /* For image texture rendering */    
237   Bool back_p;                     /* If BOX, zooming out, not in */
238 } sprite;
239
240
241 typedef struct {
242   GLXContext *glx_context;
243   int nimages;                  /* how many images are loaded or loading now */
244   image *images[10];            /* pointers to the images */
245
246   int nsprites;                 /* how many sprites are animating right now */
247   sprite *sprites[100];         /* pointers to the live sprites */
248
249   double now;                   /* current time in seconds */
250   double dawn_of_time;          /* when the program launched */
251   double image_load_time;       /* time when we last loaded a new image */
252
253   texture_font_data *font_data;
254
255   int sprite_id, image_id;      /* debugging id counters */
256
257   GLfloat grid_color[4], reticle_color[4], text_color[4];
258
259   anim_state anim_state;        /* Counters for global animation state, */
260   double anim_start, anim_duration;
261
262   Bool button_down_p;
263
264 } esper_state;
265
266 static esper_state *sss = NULL;
267
268
269 /* Command-line arguments
270  */
271 static int grid_size;
272 static int grid_thickness;
273
274 static Bool do_titles;      /* Display image titles. */
275 static GLfloat speed;
276 static Bool debug_p;        /* Be loud and do weird things. */
277
278
279 static XrmOptionDescRec opts[] = {
280   { "-speed",      ".speed",     XrmoptionSepArg, 0 },
281   { "-titles",     ".titles",    XrmoptionNoArg, "True"  },
282   { "-no-titles",  ".titles",    XrmoptionNoArg, "False" },
283   { "-debug",      ".debug",     XrmoptionNoArg, "True"  },
284 };
285
286 static argtype vars[] = {
287   { &grid_size,     "gridSize",     "GridSize",     DEF_GRID_SIZE,      t_Int},
288   { &grid_thickness,"gridThickness","GridThickness",DEF_GRID_THICKNESS, t_Int},
289   { &do_titles,     "titles",       "Titles",       DEF_TITLES,        t_Bool},
290   { &speed,         "speed",        "Speed",        DEF_SPEED,        t_Float},
291   { &debug_p,       "debug",        "Debug",        DEF_DEBUG,         t_Bool},
292 };
293
294 ENTRYPOINT ModeSpecOpt esper_opts = {countof(opts), opts, countof(vars), vars, NULL};
295
296
297 /* Returns the current time in seconds as a double.
298  */
299 static double
300 double_time (void)
301 {
302   struct timeval now;
303 # ifdef GETTIMEOFDAY_TWO_ARGS
304   struct timezone tzp;
305   gettimeofday(&now, &tzp);
306 # else
307   gettimeofday(&now);
308 # endif
309
310   return (now.tv_sec + ((double) now.tv_usec * 0.000001));
311 }
312
313 static const char *
314 state_name (anim_state s)
315 {
316   switch (s) {
317   case BLANK:         return "BLANK";
318   case GRID_ON:       return "GRID_ON";
319   case IMAGE_LOAD:    return "IMAGE_LOAD";
320   case IMAGE_UNLOAD:  return "IMAGE_UNLOAD";
321   case IMAGE_FORCE_UNLOAD: return "IMAGE_FORCE_UNLOAD";
322   case REPOSITION:    return "REPOSITION";
323   case RETICLE_ON:    return "RETICLE_ON";
324   case RETICLE_MOVE:  return "RETICLE_MOVE";
325   case BOX_MOVE:      return "BOX_MOVE";
326   case IMAGE_ZOOM:    return "IMAGE_ZOOM";
327   case MANUAL_BOX_ON: return "MANUAL_BOX_ON";
328   case MANUAL_BOX:    return "MANUAL_BOX";
329   case MANUAL_RETICLE_ON: return "MANUAL_RETICLE_ON";
330   case MANUAL_RETICLE:    return "MANUAL_RETICLE";
331   default:            return "UNKNOWN";
332   }
333 }
334
335
336 static void image_loaded_cb (const char *filename, XRectangle *geom,
337                              int image_width, int image_height,
338                              int texture_width, int texture_height,
339                              void *closure);
340
341
342 /* Allocate an image structure and start a file loading in the background.
343  */
344 static image *
345 alloc_image (ModeInfo *mi)
346 {
347   esper_state *ss = &sss[MI_SCREEN(mi)];
348   int wire = MI_IS_WIREFRAME(mi);
349   image *img = (image *) calloc (1, sizeof (*img));
350
351   img->id = ++ss->image_id;
352   img->loaded_p = False;
353   img->used_p = False;
354   img->mi = mi;
355
356   glGenTextures (1, &img->texid);
357   if (img->texid <= 0) abort();
358
359   ss->image_load_time = ss->now;
360
361   if (wire)
362     image_loaded_cb (0, 0, 0, 0, 0, 0, img);
363   else
364     {
365       /* If possible, load images at much higher resolution than the window,
366          to facilitate deep zooms.
367        */
368       int max_max = 4096;  /* ~12 megapixels */
369       int max = 0;
370
371 # if defined(HAVE_XSHM_EXTENSION) && \
372      !defined(HAVE_MOBILE) && \
373      !defined(HAVE_COCOA)
374
375       /* Try not to ask for an image larger than the SHM segment size.
376          If XSHM fails in a real-X11 world, it can take a staggeringly long
377          time to transfer the image bits from the server over Xproto -- like,
378          *18 seconds* for 4096 px and 8 seconds for 3072 px on MacOS XQuartz.
379          What madness is this?
380        */
381       unsigned long shmmax = 0;
382
383 #  if defined(SHMMAX)
384       /* Linux 2.6 defines this to be 0x2000000, but on CentOS 6.9,
385          "sysctl kernel.shmmax" reports a luxurious 0x1000000000. */
386       shmmax = SHMMAX;
387 #  elif defined(__APPLE__)
388       /* MacOS 10.13 "sysctl kern.sysv.shmmax" is paltry: */
389       shmmax = 0x400000;
390 #  endif /* !SHMMAX */
391
392       if (shmmax)
393         {
394           /* Roughly, bytes => NxN. b = (n/8)*4n = n*n*4, so n^2 = 2b, so: */
395           unsigned long n = sqrt(shmmax)/2;
396           if (n < max_max)
397             max_max = n;
398         }
399 # endif /* HAVE_XSHM_EXTENSION and real X11 */
400
401       glGetIntegerv (GL_MAX_TEXTURE_SIZE, &max);
402       if (max > max_max) max = max_max;
403
404       /* Never ask for an image smaller than the window, even if that
405          will make XSHM fall back to Xproto. */
406       if (max < MI_WIDTH(mi) || max < MI_HEIGHT(mi))
407         max = 0;
408
409       load_texture_async (mi->xgwa.screen, mi->window, *ss->glx_context,
410                           max, max, False, img->texid, image_loaded_cb, img);
411     }
412
413   ss->images[ss->nimages++] = img;
414   if (ss->nimages >= countof(ss->images)) abort();
415
416   return img;
417 }
418
419
420 /* Callback that tells us that the texture has been loaded.
421  */
422 static void
423 image_loaded_cb (const char *filename, XRectangle *geom,
424                  int image_width, int image_height,
425                  int texture_width, int texture_height,
426                  void *closure)
427 {
428   image *img = (image *) closure;
429   ModeInfo *mi = img->mi;
430   int ow, oh;
431   /* esper_state *ss = &sss[MI_SCREEN(mi)]; */
432
433   int wire = MI_IS_WIREFRAME(mi);
434
435   if (wire)
436     {
437       img->w = MI_WIDTH (mi) * (0.5 + frand (1.0));
438       img->h = MI_HEIGHT (mi);
439       img->geom.width  = img->w;
440       img->geom.height = img->h;
441       goto DONE;
442     }
443
444   if (image_width == 0 || image_height == 0)
445     exit (1);
446
447   img->w  = image_width;
448   img->h  = image_height;
449   img->tw = texture_width;
450   img->th = texture_height;
451   img->geom = *geom;
452   img->title = (filename ? strdup (filename) : 0);
453
454   ow = img->geom.width;
455   oh = img->geom.height;
456
457   /* If the image's width doesn't come back as the width of the screen,
458      then the image must have been scaled down (due to insufficient
459      texture memory.)  Scale up the coordinates to stretch the image
460      to fill the window.
461    */
462   if (img->w != MI_WIDTH(mi))
463     {
464       double scale = (double) MI_WIDTH(mi) / img->w;
465       img->w  *= scale;
466       img->h  *= scale;
467       img->tw *= scale;
468       img->th *= scale;
469       img->geom.x      *= scale;
470       img->geom.y      *= scale;
471       img->geom.width  *= scale;
472       img->geom.height *= scale;
473     }
474
475   /* xscreensaver-getimage returns paths relative to the image directory
476      now, so leave the sub-directory part in.  Unless it's an absolute path.
477   */
478   if (img->title && img->title[0] == '/')
479     {
480       /* strip filename to part between last "/" and last ".". */
481       char *s = strrchr (img->title, '/');
482       if (s) strcpy (img->title, s+1);
483       s = strrchr (img->title, '.');
484       if (s) *s = 0;
485     }
486
487 # if !(__APPLE__ && TARGET_IPHONE_SIMULATOR || !defined(__OPTIMIZE__))
488   if (debug_p)
489 # endif
490     fprintf (stderr, "%s: loaded %lu \"%s\" %dx%d\n",
491              progname, img->id, (img->title ? img->title : "(null)"),
492              ow, oh);
493  DONE:
494
495   img->loaded_p = True;
496 }
497
498
499
500 /* Free the image and texture, after nobody is referencing it.
501  */
502 static void
503 destroy_image (ModeInfo *mi, image *img)
504 {
505   esper_state *ss = &sss[MI_SCREEN(mi)];
506   Bool freed_p = False;
507   int i;
508
509   if (!img) abort();
510   if (!img->loaded_p) abort();
511   if (!img->used_p) abort();
512   if (img->texid <= 0) abort();
513   if (img->refcount != 0) abort();
514
515   for (i = 0; i < ss->nimages; i++)             /* unlink it from the list */
516     if (ss->images[i] == img)
517       {
518         int j;
519         for (j = i; j < ss->nimages-1; j++)     /* pull remainder forward */
520           ss->images[j] = ss->images[j+1];
521         ss->images[j] = 0;
522         ss->nimages--;
523         freed_p = True;
524         break;
525       }
526
527   if (!freed_p) abort();
528
529   if (debug_p)
530     fprintf (stderr, "%s: unloaded img %2lu: \"%s\"\n",
531              progname, img->id, (img->title ? img->title : "(null)"));
532
533   if (img->title) free (img->title);
534   glDeleteTextures (1, &img->texid);
535   free (img);
536 }
537
538
539 /* Return an image to use for a sprite.
540    If it's time for a new one, get a new one.
541    Otherwise, use an old one.
542    Might return 0 if the machine is really slow.
543  */
544 static image *
545 get_image (ModeInfo *mi)
546 {
547   esper_state *ss = &sss[MI_SCREEN(mi)];
548   image *img = 0;
549   image *loading_img = 0;
550   int i;
551
552   for (i = 0; i < ss->nimages; i++)
553     {
554       image *img2 = ss->images[i];
555       if (!img2) abort();
556       if (!img2->loaded_p)
557         loading_img = img2;
558       else
559         img = img2;
560     }
561
562   /* Make sure that there is always one unused image in the pipe.
563    */
564   if (!img && !loading_img)
565     alloc_image (mi);
566
567   return img;
568 }
569
570
571 /* Allocate a new sprite and start its animation going.
572  */
573 static sprite *
574 new_sprite (ModeInfo *mi, sprite_type type)
575 {
576   esper_state *ss = &sss[MI_SCREEN(mi)];
577   image *img = (type == IMAGE ? get_image (mi) : 0);
578   sprite *sp;
579
580   if (type == IMAGE && !img)
581     {
582       /* Oops, no images yet!  The machine is probably hurting bad.
583          Let's give it some time before thrashing again. */
584       usleep (250000);
585       return 0;
586     }
587
588   sp = (sprite *) calloc (1, sizeof (*sp));
589   sp->id = ++ss->sprite_id;
590   sp->type = type;
591   sp->start_time = ss->now;
592   sp->state_time = sp->start_time;
593   sp->thickness_scale = 1;
594   sp->throb_p = True;
595   sp->to.x = 0.5;
596   sp->to.y = 0.5;
597   sp->to.w = 1.0;
598   sp->to.h = 1.0;
599
600   if (img)
601     {
602       sp->img = img;
603       sp->img->refcount++;
604       sp->img->used_p = True;
605       sp->duration = 0;   /* forever, until further notice */
606       sp->fade_duration = 0.5;
607
608       /* Scale the sprite so that the image bits fill the window. */
609       {
610         double w = MI_WIDTH(mi);
611         double h = MI_HEIGHT(mi);
612         double r;
613         r = ((img->geom.height / (double) img->geom.width) * (w / h));
614         if (r > 1)
615           sp->to.h *= r;
616         else
617           sp->to.w /= r;
618       }
619
620       /* Pan to a random spot */
621       if (sp->to.h > 1)
622         sp->to.y += frand ((sp->to.h - 1) / 2) * RANDSIGN();
623       if (sp->to.w > 1)
624         sp->to.x += frand ((sp->to.w - 1) / 2) * RANDSIGN();
625     }
626
627   sp->from = sp->current = sp->to;
628
629   ss->sprites[ss->nsprites++] = sp;
630   if (ss->nsprites >= countof(ss->sprites)) abort();
631
632   return sp;
633 }
634
635
636 static sprite *
637 copy_sprite (ModeInfo *mi, sprite *old)
638 {
639   sprite *sp = new_sprite (mi, (sprite_type) ~0L);
640   int id;
641   double tt = sp->start_time;
642   if (!sp) abort();
643   id = sp->id;
644   memcpy (sp, old, sizeof(*sp));
645   sp->id = id;
646   sp->state = NEW;
647   sp->state_time = sp->start_time = tt;
648   if (sp->img)
649     sp->img->refcount++;
650   return sp;
651 }
652
653
654 /* Free the given sprite, and decrement the reference count on its image.
655  */
656 static void
657 destroy_sprite (ModeInfo *mi, sprite *sp)
658 {
659   esper_state *ss = &sss[MI_SCREEN(mi)];
660   Bool freed_p = False;
661   image *img;
662   int i;
663
664   if (!sp) abort();
665   if (sp->state != DEAD) abort();
666   img = sp->img;
667
668   if (sp->type != IMAGE)
669     {
670       if (img) abort();
671     }
672   else
673     {
674       if (!img) abort();
675       if (!img->loaded_p) abort();
676       if (!img->used_p) abort();
677       if (img->refcount <= 0) abort();
678     }
679
680   for (i = 0; i < ss->nsprites; i++)            /* unlink it from the list */
681     if (ss->sprites[i] == sp)
682       {
683         int j;
684         for (j = i; j < ss->nsprites-1; j++)    /* pull remainder forward */
685           ss->sprites[j] = ss->sprites[j+1];
686         ss->sprites[j] = 0;
687         ss->nsprites--;
688         freed_p = True;
689         break;
690       }
691
692   if (!freed_p) abort();
693   if (sp->text) free (sp->text);
694   free (sp);
695   sp = 0;
696
697   if (img)
698     {
699       img->refcount--;
700       if (img->refcount < 0) abort();
701       if (img->refcount == 0)
702         destroy_image (mi, img);
703     }
704 }
705
706
707 /* Updates the sprite for the current frame of the animation based on
708    its creation time compared to the current wall clock.
709  */
710 static void
711 tick_sprite (ModeInfo *mi, sprite *sp)
712 {
713   esper_state *ss = &sss[MI_SCREEN(mi)];
714   image *img = sp->img;
715   double now = ss->now;
716   double secs;
717   double ratio;
718   GLfloat visible = sp->duration + sp->fade_duration * 2;
719   GLfloat total = sp->pause_duration + visible;
720
721   if (sp->type != IMAGE)
722     {
723       if (sp->img) abort();
724     }
725   else
726     {
727       if (! sp->img) abort();
728       if (! img->loaded_p) abort();
729     }
730
731   /*          pause        fade  duration        fade
732      |------------|------------|---------|-----------|
733                    ....----====##########====----....
734                from             current            to
735    */
736
737   secs = now - sp->start_time;
738   ratio = (visible <= 0 ? 1 : ((secs - sp->pause_duration) / visible));
739   if (ratio < 0) ratio = 0;
740   else if (ratio > 1) ratio = 1;
741
742   sp->current.x = sp->from.x + ratio * (sp->to.x - sp->from.x);
743   sp->current.y = sp->from.y + ratio * (sp->to.y - sp->from.y);
744   sp->current.w = sp->from.w + ratio * (sp->to.w - sp->from.w);
745   sp->current.h = sp->from.h + ratio * (sp->to.h - sp->from.h);
746
747   sp->thickness_scale = 1;
748
749   if (secs < sp->pause_duration)
750     {
751       sp->state = IN;
752       sp->opacity = 0;
753     }
754   else if (secs < sp->pause_duration + sp->fade_duration)
755     {
756       sp->state = IN;
757       sp->opacity = (secs - sp->pause_duration) / (GLfloat) sp->fade_duration;
758     }
759   else if (sp->duration == 0 ||  /* 0 means infinite lifetime */
760            sp->remain_p ||
761            secs < sp->pause_duration + sp->fade_duration + sp->duration)
762     {
763       sp->state = FULL;
764       sp->opacity = 1;
765
766       /* Just after reaching full opacity, pulse the width up and down. */
767       if (sp->fade_duration > 0 &&
768           secs < sp->pause_duration + sp->fade_duration * 2)
769         {
770           GLfloat f = ((secs - (sp->pause_duration + sp->fade_duration)) /
771                        sp->fade_duration);
772           if (sp->throb_p)
773             sp->thickness_scale = 1 + 3 * (f > 0.5 ? 1-f : f);
774         }
775     }
776   else if (secs < total)
777     {
778       sp->state = OUT;
779       sp->opacity = (total - secs) / sp->fade_duration;
780     }
781   else
782     {
783       sp->state = DEAD;
784       sp->opacity = 0;
785     }
786
787   sp->frame_count++;
788 }
789
790
791 /* Draw the given sprite at the phase of its animation dictated by
792    its creation time compared to the current wall clock.
793  */
794 static void
795 draw_image_sprite (ModeInfo *mi, sprite *sp)
796 {
797   /* esper_state *ss = &sss[MI_SCREEN(mi)]; */
798   int wire = MI_IS_WIREFRAME(mi);
799   image *img = sp->img;
800
801   if (! sp->img) abort();
802   if (! img->loaded_p) abort();
803
804   glPushMatrix();
805   {
806     GLfloat s = 1 + (sp->thickness_scale - 1) / 40.0;
807     glTranslatef (0.5, 0.5, 0);
808     glScalef (s, s, 1);
809     glTranslatef (-0.5, -0.5, 0);
810
811     glTranslatef (sp->current.x, sp->current.y, 0);
812     glScalef (sp->current.w, sp->current.h, 1);
813
814     glTranslatef (-0.5, -0.5, 0);
815
816     if (wire)                   /* Draw a grid inside the box */
817       {
818         GLfloat dy = 0.1;
819         GLfloat dx = dy * img->w / img->h;
820         GLfloat x, y;
821
822         if (sp->id & 1)
823           glColor4f (sp->opacity, 0, 0, 1);
824         else
825           glColor4f (0, 0, sp->opacity, 1);
826
827         glBegin(GL_LINES);
828         glVertex3f (0, 0, 0); glVertex3f (1, 1, 0);
829         glVertex3f (1, 0, 0); glVertex3f (0, 1, 0);
830
831         for (y = 0; y < 1+dy; y += dy)
832           {
833             GLfloat yy = (y > 1 ? 1 : y);
834             for (x = 0.5; x < 1+dx; x += dx)
835               {
836                 GLfloat xx = (x > 1 ? 1 : x);
837                 glVertex3f (0, xx, 0); glVertex3f (1, xx, 0);
838                 glVertex3f (yy, 0, 0); glVertex3f (yy, 1, 0);
839               }
840             for (x = 0.5; x > -dx; x -= dx)
841               {
842                 GLfloat xx = (x < 0 ? 0 : x);
843                 glVertex3f (0, xx, 0); glVertex3f (1, xx, 0);
844                 glVertex3f (yy, 0, 0); glVertex3f (yy, 1, 0);
845               }
846           }
847         glEnd();
848       }
849     else                        /* Draw the texture quad */
850       {
851         GLfloat texw  = img->geom.width  / (GLfloat) img->tw;
852         GLfloat texh  = img->geom.height / (GLfloat) img->th;
853         GLfloat texx1 = img->geom.x / (GLfloat) img->tw;
854         GLfloat texy1 = img->geom.y / (GLfloat) img->th;
855         GLfloat texx2 = texx1 + texw;
856         GLfloat texy2 = texy1 + texh;
857         GLfloat o = sp->opacity;
858         GLint mag = (sp->fatbits_p ? GL_NEAREST : GL_LINEAR);
859
860         glBindTexture (GL_TEXTURE_2D, img->texid);
861
862         glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
863         glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
864         glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag);
865         glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, mag);
866
867         /* o = 1 - sin ((1 - o*o*o) * M_PI/2); */
868         glColor4f (1, 1, 1, o);
869
870         glNormal3f (0, 0, 1);
871         glBegin (GL_QUADS);
872         glTexCoord2f (texx1, texy2); glVertex3f (0, 0, 0);
873         glTexCoord2f (texx2, texy2); glVertex3f (1, 0, 0);
874         glTexCoord2f (texx2, texy1); glVertex3f (1, 1, 0);
875         glTexCoord2f (texx1, texy1); glVertex3f (0, 1, 0);
876         glEnd();
877
878         if (debug_p)            /* Draw a border around the image */
879           {
880             if (!wire) glDisable (GL_TEXTURE_2D);
881             glColor4f (sp->opacity, 0, 0, 1);
882             glBegin (GL_LINE_LOOP);
883             glVertex3f (0, 0, 0);
884             glVertex3f (0, 1, 0);
885             glVertex3f (1, 1, 0);
886             glVertex3f (1, 0, 0);
887             glEnd();
888             if (!wire) glEnable (GL_TEXTURE_2D);
889           }
890       }
891   }
892   glPopMatrix();
893 }
894
895
896 static void
897 draw_line_sprite (ModeInfo *mi, sprite *sp)
898 {
899   esper_state *ss = &sss[MI_SCREEN(mi)];
900   int wire = MI_IS_WIREFRAME(mi);
901   int w = MI_WIDTH(mi);
902   int h = MI_HEIGHT(mi);
903   int wh = (w > h ? w : h);
904   int gs = (sp->type == RETICLE ? grid_size+1 : grid_size);
905   int sx = wh / (gs + 1);
906   int sy;
907   int k;
908   GLfloat t = grid_thickness * sp->thickness_scale;
909   int fade;
910   GLfloat color[4];
911
912   GLfloat x  = w * sp->current.x;
913   GLfloat y  = h * sp->current.y;
914   GLfloat bw = w * sp->current.w;
915   GLfloat bh = h * sp->current.h;
916
917   if (sx < 10) sx = 10;
918   sy = sx;
919
920   if (t > sx/3) t = sx/3;
921   if (t < 1) t = 1;
922   fade = t;
923   if (fade < 1) fade = 1;
924
925   if (t <= 0 || sp->opacity <= 0) return;
926
927   glPushMatrix();
928   glLoadIdentity();
929
930   if (debug_p)
931     {
932       GLfloat s = 0.75;
933       glScalef (s, s, s);
934     }
935
936   glOrtho (0, w, 0, h, -1, 1);
937
938   switch (sp->type) {
939   case GRID:    memcpy (color, ss->grid_color,    sizeof(color)); break;
940   case RETICLE: memcpy (color, ss->reticle_color, sizeof(color)); break;
941   case BOX:     memcpy (color, ss->reticle_color, sizeof(color)); break;
942   default: abort();
943   }
944
945   if (sp->type == GRID)
946     {
947       GLfloat s = 1 + (sp->thickness_scale - 1) / 120.0;
948       glTranslatef (w/2, h/2, 0);
949       glScalef (s, s, 1);
950       glTranslatef (-w/2, -h/2, 0);
951     }
952
953   glColor4fv (color);
954
955   if (!wire) glDisable (GL_TEXTURE_2D);
956
957   for (k = 0; k < fade; k++)
958     {
959       GLfloat t2 = t * (1 - (k / (fade * 1.0)));
960       if (t2 <= 0) break;
961       color[3] = sp->opacity / fade;
962       glColor4fv (color);
963
964       glBegin (wire ? GL_LINES : GL_QUADS);
965
966       switch (sp->type) {
967       case GRID:
968         {
969           GLfloat xoff = (w - sx * (w / sx)) / 2.0;
970           GLfloat yoff = (h - sy * (h / sy)) / 2.0;
971           for (y = -sy/2+t2/2; y < h; y += sy)
972             for (x = -sx/2-t2/2; x < w; x += sx)
973               {
974                 glVertex3f (xoff+x+t2, yoff+y,       0);
975                 glVertex3f (xoff+x+t2, yoff+y+sy-t2, 0);
976                 glVertex3f (xoff+x,    yoff+y+sy-t2, 0);
977                 glVertex3f (xoff+x,    yoff+y,       0);
978                 mi->polygon_count++;
979
980                 glVertex3f (xoff+x,    yoff+y-t2, 0);
981                 glVertex3f (xoff+x+sx, yoff+y-t2, 0);
982                 glVertex3f (xoff+x+sx, yoff+y,    0);
983                 glVertex3f (xoff+x,    yoff+y,    0);
984                 mi->polygon_count++;
985               }
986         }
987         break;
988
989       case BOX:
990         glVertex3f (x-bw/2-t2/2, y-bh/2-t2/2, 0);
991         glVertex3f (x+bw/2+t2/2, y-bh/2-t2/2, 0);
992         glVertex3f (x+bw/2+t2/2, y-bh/2+t2/2, 0);
993         glVertex3f (x-bw/2-t2/2, y-bh/2+t2/2, 0);
994         mi->polygon_count++;
995
996         glVertex3f (x-bw/2-t2/2, y+bh/2-t2/2, 0);
997         glVertex3f (x+bw/2+t2/2, y+bh/2-t2/2, 0);
998         glVertex3f (x+bw/2+t2/2, y+bh/2+t2/2, 0);
999         glVertex3f (x-bw/2-t2/2, y+bh/2+t2/2, 0);
1000         mi->polygon_count++;
1001
1002         glVertex3f (x-bw/2+t2/2, y-bh/2+t2/2, 0);
1003         glVertex3f (x-bw/2+t2/2, y+bh/2-t2/2, 0);
1004         glVertex3f (x-bw/2-t2/2, y+bh/2-t2/2, 0);
1005         glVertex3f (x-bw/2-t2/2, y-bh/2+t2/2, 0);
1006         mi->polygon_count++;
1007
1008         glVertex3f (x+bw/2+t2/2, y-bh/2+t2/2, 0);
1009         glVertex3f (x+bw/2+t2/2, y+bh/2-t2/2, 0);
1010         glVertex3f (x+bw/2-t2/2, y+bh/2-t2/2, 0);
1011         glVertex3f (x+bw/2-t2/2, y-bh/2+t2/2, 0);
1012         mi->polygon_count++;
1013         break;
1014
1015       case RETICLE:
1016         glVertex3f (x+t2/2, y+sy/2-t2/2, 0);
1017         glVertex3f (x+t2/2, h,           0);
1018         glVertex3f (x-t2/2, h,           0);
1019         glVertex3f (x-t2/2, y+sy/2-t2/2, 0);
1020         mi->polygon_count++;
1021
1022         glVertex3f (x-t2/2, y-sy/2+t2/2, 0);
1023         glVertex3f (x-t2/2, 0,           0);
1024         glVertex3f (x+t2/2, 0,           0);
1025         glVertex3f (x+t2/2, y-sy/2+t2/2, 0);
1026         mi->polygon_count++;
1027
1028         glVertex3f (x-sx/2+t2/2, y+t2/2, 0);
1029         glVertex3f (0,           y+t2/2, 0);
1030         glVertex3f (0,           y-t2/2, 0);
1031         glVertex3f (x-sx/2+t2/2, y-t2/2, 0);
1032         mi->polygon_count++;
1033
1034         glVertex3f (x+sx/2-t2/2, y-t2/2, 0);
1035         glVertex3f (w,           y-t2/2, 0);
1036         glVertex3f (w,           y+t2/2, 0);
1037         glVertex3f (x+sx/2-t2/2, y+t2/2, 0);
1038         mi->polygon_count++;
1039         break;
1040
1041       default: abort();
1042       }
1043       glEnd();
1044     }
1045
1046   glPopMatrix();
1047
1048   if (!wire) glEnable (GL_TEXTURE_2D);
1049 }
1050
1051
1052 static sprite * find_newest_sprite (ModeInfo *, sprite_type);
1053 static void compute_image_rect (rect *, sprite *, Bool);
1054
1055 static void
1056 draw_text_sprite (ModeInfo *mi, sprite *sp)
1057 {
1058   esper_state *ss = &sss[MI_SCREEN(mi)];
1059   int wire = MI_IS_WIREFRAME(mi);
1060   GLfloat w = MI_WIDTH(mi);
1061   GLfloat h = MI_HEIGHT(mi);
1062   GLfloat s;
1063   int x, y, z;
1064   XCharStruct e;
1065   sprite *target = 0;
1066   char text[255];
1067   GLfloat color[4];
1068   int i;
1069
1070   if (sp->opacity <= 0)
1071     return;
1072
1073   for (i = 0; i < ss->nsprites; i++)
1074     {
1075       sprite *sp2 = ss->sprites[i];
1076       if (sp2->id == sp->text_id && sp2->state != DEAD)
1077         {
1078           target = sp2;
1079           break;
1080         }
1081     }
1082
1083   if (target)
1084     {
1085       rect r;
1086       sprite *img;
1087
1088       if (target->opacity <= 0 && 
1089           (target->state == NEW || target->state == IN))
1090         return;
1091
1092       r = target->current;
1093
1094       img = find_newest_sprite (mi, IMAGE);
1095       if (img)
1096         compute_image_rect (&r, img, target->back_p);
1097
1098       mi->recursion_depth = (img
1099                              ? MIN (img->current.w, img->current.h)
1100                              : 0);
1101
1102       x = abs ((int) (r.x * 10000)) % 10000;
1103       y = abs ((int) (r.y * 10000)) % 10000;
1104       z = abs ((int) (r.w * 10000)) % 10000;
1105
1106       sprintf (text, "ZM %04d  NS %04d  EW %04d", z, y, x);
1107
1108       if ((x == 0 || x == 5000) &&              /* startup */
1109           (y == 0 || y == 5000) &&
1110           (z == 0 || z == 5000))
1111         *text = 0;
1112
1113       if (do_titles && 
1114           target->type == IMAGE &&
1115           target->remain_p)  /* The initial background image */
1116         {
1117           char *s = (target->img &&
1118                      target->img->title && *target->img->title
1119                      ? target->img->title
1120                      : "Loading");
1121           int L = strlen (s);
1122           int i = (L > 23 ? L-23 : 0);
1123           sprintf (text, ">>%-23s", target->img->title + i);
1124           for (s = text; *s; s++)
1125             if (*s >= 'a' && *s <= 'z') *s += ('A'-'a');
1126             else if (*s == '/' || *s == '-' || *s == '.') *s = '_';
1127         }
1128
1129       if (!*text) return;
1130
1131       if (sp->text) free (sp->text);
1132       sp->text = strdup (text);
1133     }
1134   else if (sp->text && *sp->text)
1135     /* The target sprite might be dead, but we saved our last text. */
1136     strcpy (text, sp->text);
1137   else
1138     /* No target, no saved text. */
1139     return;
1140
1141   texture_string_metrics (ss->font_data, text, &e, 0, 0);
1142
1143   glPushMatrix();
1144   glLoadIdentity();
1145   glOrtho (0, 1, 0, 1, -1, 1);
1146
1147   /* Scale the text to fit N characters horizontally. */
1148   {
1149 # ifdef HAVE_MOBILE
1150     GLfloat c = 25;
1151 # else /* desktop */
1152     GLfloat c = (MI_WIDTH(mi) <= 640  ? 25 :
1153                  MI_WIDTH(mi) <= 1280 ? 32 : 64);
1154 # endif
1155     s = w / (e.ascent * c);
1156   }
1157   w /= s;
1158   h /= s;
1159   x = (w - e.width) / 2;
1160   y = e.ascent + e.descent * 2;
1161
1162   glScalef (1.0/w, 1.0/h, 1);
1163   glTranslatef (x, y, 0);
1164
1165   memcpy (color, ss->text_color, sizeof(color));
1166   color[3] = sp->opacity;
1167   glColor4fv (color);
1168
1169   if (wire)
1170     glEnable (GL_TEXTURE_2D);
1171
1172   print_texture_string (ss->font_data, text);
1173   mi->polygon_count++;
1174
1175   if (wire)
1176     glDisable (GL_TEXTURE_2D);
1177   glPopMatrix();
1178 }
1179
1180
1181 static void
1182 draw_flash_sprite (ModeInfo *mi, sprite *sp)
1183 {
1184   /* esper_state *ss = &sss[MI_SCREEN(mi)]; */
1185   GLfloat o = sp->opacity;
1186
1187   if (o <= 0) return;
1188   o = 0.7;  /* Too fast to see, so keep it consistent */
1189
1190   glPushMatrix();
1191   int wire = MI_IS_WIREFRAME(mi);
1192   if (!wire)
1193     glDisable (GL_TEXTURE_2D);
1194   glColor4f (0, 0, 1, o);
1195   glColorMask (0, 0, 1, 1); /* write only into blue and alpha channels */
1196   glBegin (GL_QUADS);
1197   glVertex3f (0, 0, 0);
1198   glVertex3f (1, 0, 0);
1199   glVertex3f (1, 1, 0);
1200   glVertex3f (0, 1, 0);
1201   glEnd();
1202   glColorMask (1, 1, 1, 1);
1203   if (!wire)
1204     glEnable (GL_TEXTURE_2D);
1205   glPopMatrix();
1206 }
1207
1208
1209 static void
1210 draw_sprite (ModeInfo *mi, sprite *sp)
1211 {
1212   switch (sp->type) {
1213   case IMAGE:
1214     draw_image_sprite (mi, sp);
1215     break;
1216   case RETICLE:
1217   case BOX:
1218   case GRID:
1219     draw_line_sprite (mi, sp);
1220     break;
1221   case TEXT:
1222     draw_text_sprite (mi, sp);
1223     break;
1224   case FLASH:
1225     draw_flash_sprite (mi, sp);
1226     break;
1227   default:
1228     abort();
1229   }
1230 }
1231
1232
1233 static void
1234 tick_sprites (ModeInfo *mi)
1235 {
1236   esper_state *ss = &sss[MI_SCREEN(mi)];
1237   int i;
1238   for (i = 0; i < ss->nsprites; i++)
1239     tick_sprite (mi, ss->sprites[i]);
1240
1241   for (i = 0; i < ss->nsprites; i++)
1242     {
1243       sprite *sp = ss->sprites[i];
1244       if (sp->state == DEAD)
1245         {
1246           destroy_sprite (mi, sp);
1247           i--;
1248         }
1249     }
1250 }
1251
1252
1253 static void
1254 draw_sprites (ModeInfo *mi)
1255 {
1256   esper_state *ss = &sss[MI_SCREEN(mi)];
1257   int i;
1258
1259   glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1260
1261   glPushMatrix();
1262
1263 /*
1264   {
1265     GLfloat rot = current_device_rotation();
1266     glTranslatef (0.5, 0.5, 0);
1267     glRotatef(rot, 0, 0, 1);
1268     if ((rot >  45 && rot <  135) ||
1269         (rot < -45 && rot > -135))
1270       {
1271         GLfloat s = MI_WIDTH(mi) / (GLfloat) MI_HEIGHT(mi);
1272         glScalef (s, 1/s, 1);
1273       }
1274     glTranslatef (-0.5, -0.5, 0);
1275   }
1276 */
1277
1278   /* Draw the images first, then the overlays. */
1279   for (i = 0; i < ss->nsprites; i++)
1280     if (ss->sprites[i]->type == IMAGE)
1281       draw_sprite (mi, ss->sprites[i]);
1282   for (i = 0; i < ss->nsprites; i++)
1283     if (ss->sprites[i]->type != IMAGE)
1284       draw_sprite (mi, ss->sprites[i]);
1285
1286   glPopMatrix();
1287
1288   if (debug_p)                          /* draw a white box (the "screen") */
1289     {
1290       int wire = MI_IS_WIREFRAME(mi);
1291
1292       if (!wire) glDisable (GL_TEXTURE_2D);
1293
1294       glColor4f (1, 1, 1, 1);
1295       glBegin (GL_LINE_LOOP);
1296       glVertex3f (0, 0, 0);
1297       glVertex3f (0, 1, 0);
1298       glVertex3f (1, 1, 0);
1299       glVertex3f (1, 0, 0);
1300       glEnd();
1301
1302       if (!wire) glEnable (GL_TEXTURE_2D);
1303     }
1304 }
1305
1306
1307 static void
1308 fadeout_sprite (ModeInfo *mi, sprite *sp)
1309 {
1310   esper_state *ss = &sss[MI_SCREEN(mi)];
1311
1312   /* If it hasn't faded in yet, don't fade out. */
1313   if (ss->now <= sp->start_time + sp->pause_duration)
1314     sp->fade_duration = 0;
1315
1316   /* Pretend it's at the point where it should fade out. */
1317   sp->pause_duration = 0;
1318   sp->duration = 9999;
1319   sp->remain_p = False;
1320   sp->start_time = ss->now - sp->duration;
1321 }
1322
1323 static void
1324 fadeout_sprites (ModeInfo *mi, sprite_type type)
1325 {
1326   esper_state *ss = &sss[MI_SCREEN(mi)];
1327   int i;
1328   for (i = 0; i < ss->nsprites; i++)
1329     {
1330       sprite *sp = ss->sprites[i];
1331       if (sp->type == type)
1332         fadeout_sprite (mi, sp);
1333     }
1334 }
1335
1336
1337 static sprite *
1338 find_newest_sprite (ModeInfo *mi, sprite_type type)
1339 {
1340   esper_state *ss = &sss[MI_SCREEN(mi)];
1341   sprite *sp = 0;
1342   int i;
1343   for (i = 0; i < ss->nsprites; i++)
1344     {
1345       sprite *sp2 = ss->sprites[i];
1346       if (sp2->type == type &&
1347           (!sp || 
1348            (sp->start_time < sp2->start_time &&
1349             ss->now >= sp2->start_time + sp2->pause_duration)))
1350         sp = sp2;
1351     }
1352   return sp;
1353 }
1354
1355
1356 /* Enqueue a text sprite describing the given sprite that runs at the 
1357    same time.
1358  */
1359 static sprite *
1360 push_text_sprite (ModeInfo *mi, sprite *sp)
1361 {
1362   /* esper_state *ss = &sss[MI_SCREEN(mi)]; */
1363   sprite *sp2 = new_sprite (mi, TEXT);
1364   if (!sp2) abort();
1365   sp2->text_id = sp->id;
1366   sp2->fade_duration  = sp->fade_duration;
1367   sp2->duration       = sp->duration;
1368   sp2->pause_duration = sp->pause_duration;
1369   return sp2;
1370 }
1371
1372
1373 /* Enqueue a flash sprite that fires at the same time.
1374  */
1375 #ifndef SMOOTH
1376 static sprite *
1377 push_flash_sprite (ModeInfo *mi, sprite *sp)
1378 {
1379   /* esper_state *ss = &sss[MI_SCREEN(mi)]; */
1380   sprite *sp2 = new_sprite (mi, FLASH);
1381   if (!sp2) abort();
1382   if (sp->type != IMAGE) abort();
1383   sp2->text_id = sp->id;
1384   sp2->duration       = MAX (0.07 / speed, 0.07);
1385   sp2->fade_duration  = 0;  /* Fading these is too fast to see */
1386   sp2->pause_duration = sp->pause_duration + (sp->fade_duration * 0.3);
1387   return sp2;
1388 }
1389 #endif /* !SMOOTH */
1390
1391
1392 /* Set the sprite's duration based on distance travelled.
1393  */
1394 static void
1395 compute_sprite_duration (ModeInfo *mi, sprite *sp, Bool blink_p)
1396 {
1397   /* Compute max distance traveled by any point (corners or center). */
1398   /* (cpp is the devil) */
1399 # define L(F)    (sp->F.x - sp->F.w/2)   /* delta of left edge, from/to */
1400 # define R(F) (1-(sp->F.x + sp->F.w/2))  /* right */
1401 # define B(F)    (sp->F.y - sp->F.h/2)   /* top */
1402 # define T(F) (1-(sp->F.y + sp->F.h/2))  /* bottom */
1403 # define D(F,G) sqrt(F(from)*F(from) + G(to)*G(to))  /* corner traveled */
1404   double BL = D(B,L);
1405   double BR = D(B,R);
1406   double TL = D(T,L);
1407   double TR = D(T,R);
1408   double cx = sp->to.x - sp->from.x;
1409   double cy = sp->to.y - sp->from.y;
1410   double C  = sqrt(cx*cx + cy*cy);
1411   double dist = MAX (BL, MAX (BR, MAX (TL, MAX (TR, C))));
1412 # undef L
1413 # undef R
1414 # undef B
1415 # undef T
1416 # undef D
1417
1418   int steps = 1 + dist * 28;
1419   if (steps > 10) steps = 10;
1420
1421   sp->duration = steps * 0.2 / speed;
1422
1423 # ifndef SMOOTH
1424   sp->duration += 1.5 / speed;  /* For linger added by animate_sprite_path() */
1425   if (blink_p) sp->duration += 0.6 / speed;
1426 # endif
1427 }
1428
1429
1430 /* Convert the sprite to a jerky transition.
1431    Instead of smoothly animating, move in discrete steps,
1432    using multiple staggered sprites.
1433  */
1434 static void
1435 animate_sprite_path (ModeInfo *mi, sprite *sp, Bool blink_p)
1436 {
1437 # ifndef SMOOTH
1438   /* esper_state *ss = &sss[MI_SCREEN(mi)]; */
1439   double dx = sp->to.x - sp->from.x;
1440   double dy = sp->to.y - sp->from.y;
1441   double dw = sp->to.w - sp->from.w;
1442   double dh = sp->to.h - sp->from.h;
1443   double linger  = 1.5 / speed;
1444   double blinger = 0.6 / speed;
1445   double dur = sp->duration - linger - (blink_p ? blinger : 0);
1446   int steps = dur / 0.3 * speed;  /* step duration in seconds */
1447   int i;
1448
1449   if (sp->type == IMAGE)
1450     steps *= 0.8;
1451
1452   if (steps < 2)  steps = 2;
1453   if (steps > 10) steps = 10;
1454
1455   /* if (dur <= 0.01) abort(); */
1456   if (dur < 0.01)
1457     linger = blinger = 0;
1458
1459   for (i = 0; i <= steps; i++)
1460     {
1461       sprite *sp2 = copy_sprite (mi, sp);
1462       if (!sp2) abort();
1463
1464       sp2->to.x = (sp->current.x + i * dx / steps);
1465       sp2->to.y = (sp->current.y + i * dy / steps);
1466       sp2->to.w = (sp->current.w + i * dw / steps);
1467       sp2->to.h = (sp->current.h + i * dh / steps);
1468       sp2->current = sp2->from = sp2->to;
1469       sp2->duration = dur / steps;
1470       sp2->pause_duration += i * sp2->duration;
1471       sp2->remain_p = False;
1472       sp2->fatbits_p = True;
1473
1474       if (i == steps)
1475         sp2->duration += linger;        /* last one lingers for a bit */
1476
1477       if (i == steps && !blink_p)
1478         {
1479           sp2->remain_p = sp->remain_p;
1480           sp2->fatbits_p = False;
1481         }
1482
1483       if (sp2->type == IMAGE && i > 0)
1484         push_flash_sprite (mi, sp2);
1485
1486       if (sp2->type == RETICLE || sp2->type == BOX)
1487         {
1488           sp2 = push_text_sprite (mi, sp2);
1489           if (i == steps)
1490             sp2->duration += linger * 2;
1491         }
1492     }
1493
1494   if (blink_p && blinger)               /* last one blinks before vanishing */
1495     {
1496       int blinkers = 3;
1497       for (i = 1; i <= blinkers; i++)
1498         {
1499           sprite *sp2 = copy_sprite (mi, sp);
1500           if (!sp2) abort();
1501
1502           sp2->current = sp2->from = sp->to;
1503           sp2->duration = blinger / blinkers;
1504           sp2->pause_duration += dur + linger + i * sp2->duration;
1505           sp2->remain_p = False;
1506           if (i == blinkers)
1507             {
1508               sp2->remain_p = sp->remain_p;
1509               sp2->fatbits_p = False;
1510             }
1511         }
1512     }
1513
1514   /* Fade out the template sprite. It might not have even appeared yet. */
1515   fadeout_sprite (mi, sp);
1516 # endif
1517 }
1518
1519
1520 /* Input rect is of a reticle or box.
1521    Output rect is what the image's rect should be so that the only part
1522    visible is the part indicated by the input rect.
1523  */
1524 static void
1525 compute_image_rect (rect *r, sprite *img, Bool inverse_p)
1526 {
1527   double scale = (inverse_p ? 1/r->w : r->w);
1528   double dx = r->x - 0.5;
1529   double dy = r->y - 0.5;
1530
1531   /* Adjust size and center by zoom factor */
1532   r->w = img->current.w / scale;
1533   r->h = img->current.h / scale;
1534   r->x = 0.5 + (img->current.x - 0.5) / scale;
1535   r->y = 0.5 + (img->current.y - 0.5) / scale;
1536
1537   /* Move center */
1538
1539   if (inverse_p)
1540     {
1541       dx = -dx;         /* #### Close but not quite right */
1542       dy = -dy;
1543     }
1544
1545   r->x -= dx / scale;
1546   r->y -= dy / scale;
1547 }
1548
1549
1550 /* Sets 'to' such that the image zooms out so that the only part visible
1551    is the part indicated by the box.
1552  */
1553 static void
1554 track_box_with_image (ModeInfo *mi, sprite *sp, sprite *img)
1555 {
1556   rect r = sp->current;
1557   compute_image_rect (&r, img, sp->back_p);
1558   img->to = r;
1559
1560   /* Never zoom out too far. */
1561   if (img->to.w < 1 && img->to.h < 1)
1562     {
1563       if (img->to.w > img->to.h)
1564         {
1565           img->to.w = img->to.w / img->to.h;
1566           img->to.h = 1;
1567         }
1568       else
1569         {
1570           img->to.h = img->to.h / img->to.w;
1571           img->to.w = 1;
1572         }
1573     }
1574
1575   /* Never pan beyond the bounds of the image. */
1576   if (img->to.x < -img->to.w/2+1) img->to.x = -img->to.w/2+1;
1577   if (img->to.x >  img->to.w/2)   img->to.x =  img->to.w/2;
1578   if (img->to.y < -img->to.h/2+1) img->to.y = -img->to.h/2+1;
1579   if (img->to.y >  img->to.h/2)   img->to.y =  img->to.h/2;
1580 }
1581
1582
1583 static void
1584 tick_animation (ModeInfo *mi)
1585 {
1586   esper_state *ss = &sss[MI_SCREEN(mi)];
1587   anim_state prev_state = ss->anim_state;
1588   sprite *sp = 0;
1589   int i;
1590
1591   switch (ss->anim_state) {
1592   case BLANK:
1593     ss->anim_state = GRID_ON;
1594     break;
1595   case GRID_ON:
1596     ss->anim_state = IMAGE_LOAD;
1597     break;
1598   case IMAGE_LOAD:
1599     /* Only advance once an image has loaded. */
1600     if (find_newest_sprite (mi, IMAGE))
1601       ss->anim_state = RETICLE_ON;
1602     else
1603       ss->anim_state = IMAGE_LOAD;
1604     break;
1605   case RETICLE_ON:
1606     ss->anim_state = RETICLE_MOVE;
1607     break;
1608   case RETICLE_MOVE:
1609     if (random() % 6)
1610       ss->anim_state = BOX_MOVE;
1611     else
1612       ss->anim_state = IMAGE_ZOOM;
1613     break;
1614   case BOX_MOVE:
1615     ss->anim_state = IMAGE_ZOOM;
1616     break;
1617   case IMAGE_ZOOM:
1618     {
1619       sprite *sp = find_newest_sprite (mi, IMAGE);
1620       double depth = (sp
1621                       ? MIN (sp->current.w, sp->current.h)
1622                       : 0);
1623       if (depth > 20)
1624         ss->anim_state = IMAGE_UNLOAD;
1625       else
1626         ss->anim_state = RETICLE_ON;
1627     }
1628     break;
1629   case IMAGE_FORCE_UNLOAD:
1630     ss->anim_state = IMAGE_UNLOAD;
1631     break;
1632   case IMAGE_UNLOAD:
1633     ss->anim_state = IMAGE_LOAD;
1634     break;
1635   case MANUAL_BOX_ON:
1636     ss->anim_state = MANUAL_BOX;
1637     break;
1638   case MANUAL_BOX:
1639     break;
1640   case MANUAL_RETICLE_ON:
1641     ss->anim_state = MANUAL_RETICLE;
1642     break;
1643   case MANUAL_RETICLE:
1644     break;
1645   default:
1646     abort();
1647     break;
1648   }
1649
1650   ss->anim_start = ss->now;
1651   ss->anim_duration = 0;
1652
1653   if (debug_p)
1654     fprintf (stderr, "%s: entering %s\n",
1655              progname, state_name (ss->anim_state));
1656
1657   switch (ss->anim_state) {
1658
1659   case GRID_ON:         /* Start the grid fading in. */
1660     if (! find_newest_sprite (mi, GRID))
1661       {
1662         sp = new_sprite (mi, GRID);
1663         if (!sp) abort();
1664         sp->fade_duration = 1.0 / speed;
1665         sp->duration = 2.0 / speed;
1666         sp->remain_p = True;
1667         ss->anim_duration = (sp->pause_duration + sp->fade_duration * 2 +
1668                              sp->duration);
1669       }
1670     break;
1671
1672   case IMAGE_LOAD:
1673     fadeout_sprites (mi, IMAGE);
1674     sp = new_sprite (mi, IMAGE);
1675     if (! sp) 
1676       {
1677         if (debug_p) fprintf (stderr, "%s: image load failed\n", progname);
1678         break;
1679       }
1680
1681     sp->fade_duration = 0.5 / speed;
1682     sp->duration = sp->fade_duration * 3;
1683     sp->remain_p = True;
1684     /* If we zoom in, we lose the pulse at the end. */
1685     /* sp->from.w = sp->from.h = 0.0001; */
1686     sp->current = sp->from;
1687
1688     ss->anim_duration = (sp->pause_duration + sp->fade_duration * 2 +
1689                          sp->duration);
1690
1691     sp = push_text_sprite (mi, sp);
1692     sp->fade_duration = 0.2 / speed;
1693     sp->pause_duration = 0;
1694     sp->duration = 2.5 / speed;
1695     break;
1696
1697   case IMAGE_FORCE_UNLOAD:
1698     break;
1699
1700   case IMAGE_UNLOAD:
1701     sp = find_newest_sprite (mi, IMAGE);    
1702     if (sp)
1703       sp->fade_duration = ((prev_state == IMAGE_FORCE_UNLOAD ? 0.2 : 3.0)
1704                            / speed);
1705     fadeout_sprites (mi, IMAGE);
1706     fadeout_sprites (mi, RETICLE);
1707     fadeout_sprites (mi, BOX);
1708     fadeout_sprites (mi, TEXT);
1709     ss->anim_duration = (sp ? sp->fade_duration : 0) + 3.5 / speed;
1710     break;
1711
1712   case RETICLE_ON:              /* Display reticle at center. */
1713     fadeout_sprites (mi, TEXT);
1714     sp = new_sprite (mi, RETICLE);
1715     if (!sp) abort();
1716     sp->fade_duration  = 0.2 / speed;
1717     sp->pause_duration = 1.0 / speed;
1718     sp->duration       = 1.5 / speed;
1719     ss->anim_duration = (sp->pause_duration + sp->fade_duration * 2 +
1720                          sp->duration);
1721     ss->anim_duration -= sp->fade_duration * 2;
1722     break;
1723
1724   case RETICLE_MOVE:
1725     /* Reticle has faded in.  Now move it to somewhere else.
1726        Create N new reticle sprites, wih staggered pause_durations.
1727      */
1728     {
1729       GLfloat ox = 0.5;
1730       GLfloat oy = 0.5;
1731       GLfloat nx, ny, dist;
1732
1733       do {              /* pick a new position not too near the old */
1734         nx = 0.3 + BELLRAND(0.4);
1735         ny = 0.3 + BELLRAND(0.4);
1736         dist = sqrt ((nx-ox)*(nx-ox) + (ny-oy)*(ny-oy));
1737       } while (dist < 0.1);
1738
1739       sp = new_sprite (mi, RETICLE);
1740       if (!sp) abort();
1741
1742       sp->from.x = ox;
1743       sp->from.y = oy;
1744       sp->current = sp->to = sp->from;
1745       sp->to.x = nx;
1746       sp->to.y = ny;
1747       sp->fade_duration  = 0.2 / speed;
1748       sp->pause_duration = 0;
1749       compute_sprite_duration (mi, sp, False);
1750
1751       ss->anim_duration = (sp->pause_duration + sp->fade_duration * 2 +
1752                            sp->duration - 0.1);
1753       animate_sprite_path (mi, sp, False);
1754     }
1755     break;
1756
1757   case BOX_MOVE:
1758     /* Reticle has moved, and faded out.
1759        Start the box zooming into place.
1760      */
1761     {
1762       GLfloat ox = 0.5;
1763       GLfloat oy = 0.5;
1764       GLfloat nx, ny;
1765       GLfloat z;
1766
1767       /* Find the last-added reticle, for our destination position. */
1768       sp = 0;
1769       for (i = 0; i < ss->nsprites; i++)
1770         {
1771           sprite *sp2 = ss->sprites[i];
1772           if (sp2->type == RETICLE &&
1773               (!sp || sp->start_time < sp2->start_time))
1774             sp = sp2;
1775         }
1776       if (sp)
1777         {
1778           nx = sp->to.x;
1779           ny = sp->to.y;
1780         }
1781       else
1782         {
1783           nx = ny = 0.5;
1784           if (debug_p)
1785             fprintf (stderr, "%s: no reticle before box?\n", progname);
1786         }
1787
1788       z = 0.3 + frand(0.5);
1789
1790       /* Ensure that the selected box is contained within the screen */
1791       {
1792         double margin = 0.005;
1793         double maxw = 2 * MIN (1 - margin - nx, nx - margin);
1794         double maxh = 2 * MIN (1 - margin - ny, ny - margin);
1795         double max = MIN (maxw, maxh);
1796         if (z > max) z = max;
1797       }
1798
1799       sp = new_sprite (mi, BOX);
1800       if (!sp) abort();
1801       sp->from.x = ox;
1802       sp->from.y = oy;
1803       sp->from.w = 1.0;
1804       sp->from.h = 1.0;
1805       sp->current = sp->from;
1806       sp->to.x = nx;
1807       sp->to.y = ny;
1808       sp->to.w = z;
1809       sp->to.h = z;
1810
1811       /* Maybe zoom out instead of in.
1812        */
1813       {
1814         sprite *img = find_newest_sprite (mi, IMAGE);
1815         double depth = MIN (img->current.w, img->current.h);
1816         if (depth > 1 &&  /* if zoomed in */
1817             (depth < 6 ?  !(random() % 5) :   /* 20% */
1818              depth < 12 ? !(random() % 2) :   /* 50% */
1819              (random() % 3)))                 /* 66% */
1820           {
1821             sp->back_p = True;
1822             if (depth < 1.5 && z < 0.8)
1823               {
1824                 z = 0.8;        /* don't zoom out much past 100% */
1825                 sp->to.w = z;
1826                 sp->to.h = z;
1827               }
1828           }
1829       }
1830
1831       sp->fade_duration  = 0.2 / speed;
1832       sp->pause_duration = 2.0 / speed;
1833       compute_sprite_duration (mi, sp, True);
1834       ss->anim_duration = (sp->pause_duration + sp->fade_duration * 2 +
1835                            sp->duration - 0.1);
1836       animate_sprite_path (mi, sp, True);
1837     }
1838     break;
1839
1840   case IMAGE_ZOOM:
1841
1842     /* Box has moved, and faded out.
1843        Or, if no box, then just a reticle.
1844        Zoom the underlying image to track the box's position. */
1845     {
1846       sprite *img, *img2;
1847
1848       /* Find latest box or reticle, for our destination position. */
1849       sp = find_newest_sprite (mi, BOX);
1850       if (! sp)
1851         sp = find_newest_sprite (mi, RETICLE);
1852       if (! sp)
1853         {
1854           if (debug_p)
1855             fprintf (stderr, "%s: no box or reticle before image\n",
1856                      progname);
1857           break;
1858         }
1859
1860       img = find_newest_sprite (mi, IMAGE);
1861       if (!img)
1862         {
1863           if (debug_p)
1864             fprintf (stderr, "%s: no image?\n", progname);
1865           break;
1866         }
1867
1868       img2 = copy_sprite (mi, img);
1869       if (!img2) abort();
1870
1871       img2->from = img->current;
1872
1873       fadeout_sprite (mi, img);
1874
1875       track_box_with_image (mi, sp, img2);
1876
1877       img2->fade_duration  = 0.2 / speed;
1878       img2->pause_duration = 0.5 / speed;
1879       img2->remain_p = True;
1880       img2->throb_p = False;
1881       compute_sprite_duration (mi, img2, False);
1882
1883       img->start_time += img2->pause_duration;
1884
1885       ss->anim_duration = (img2->pause_duration + img2->fade_duration * 2 +
1886                            img2->duration);
1887       animate_sprite_path (mi, img2, False);
1888       fadeout_sprites (mi, TEXT);
1889     }
1890     break;
1891
1892   case MANUAL_BOX_ON:
1893   case MANUAL_RETICLE_ON:
1894     break;
1895
1896   case MANUAL_BOX:
1897   case MANUAL_RETICLE:
1898     {
1899       sprite_type tt = (ss->anim_state == MANUAL_BOX ? BOX : RETICLE);
1900       sprite *osp = find_newest_sprite (mi, tt);
1901       fadeout_sprites (mi, RETICLE);
1902       fadeout_sprites (mi, BOX);
1903
1904       sp = new_sprite (mi, tt);
1905       if (!sp) abort();
1906       if (osp)
1907         sp->from = osp->current;
1908       else
1909         {
1910           sp->from.x = 0.5;
1911           sp->from.y = 0.5;
1912           sp->from.w = 0.5;
1913           sp->from.h = 0.5;
1914         }
1915       sp->to = sp->current = sp->from;
1916       sp->fade_duration = 0.2 / speed;
1917       sp->duration = 0.2 / speed;
1918       sp->remain_p = True;
1919       sp->throb_p = False;
1920       ss->anim_duration = 9999;
1921     }
1922     break;
1923
1924   default:
1925     fprintf (stderr,"%s: unknown state %d\n",
1926              progname, (int) ss->anim_state);
1927     abort();
1928   }
1929 }
1930
1931
1932 /* Copied from gltrackball.c, sigh.
1933  */
1934 static void
1935 adjust_for_device_rotation (double *x, double *y, double *w, double *h)
1936 {
1937   int rot = (int) current_device_rotation();
1938   int swap;
1939
1940   while (rot <= -180) rot += 360;
1941   while (rot >   180) rot -= 360;
1942
1943   if (rot > 135 || rot < -135)          /* 180 */
1944     {
1945       *x = *w - *x;
1946       *y = *h - *y;
1947     }
1948   else if (rot > 45)                    /* 90 */
1949     {
1950       swap = *x; *x = *y; *y = swap;
1951       swap = *w; *w = *h; *h = swap;
1952       *x = *w - *x;
1953     }
1954   else if (rot < -45)                   /* 270 */
1955     {
1956       swap = *x; *x = *y; *y = swap;
1957       swap = *w; *w = *h; *h = swap;
1958       *y = *h - *y;
1959     }
1960 }
1961
1962
1963 ENTRYPOINT Bool
1964 esper_handle_event (ModeInfo *mi, XEvent *event)
1965 {
1966   esper_state *ss = &sss[MI_SCREEN(mi)];
1967
1968   if (event->xany.type == Expose ||
1969       event->xany.type == GraphicsExpose ||
1970       event->xany.type == VisibilityNotify)
1971     {
1972       return False;
1973     }
1974   else if (event->xany.type == KeyPress)
1975     {
1976       KeySym keysym;
1977       char c = 0;
1978       sprite *sp = 0;
1979       double delta = 0.025;
1980       double margin = 0.005;
1981       Bool ok = False;
1982
1983       XLookupString (&event->xkey, &c, 1, &keysym, 0);
1984
1985       if (c == '\t')
1986         {
1987           ss->anim_state = IMAGE_FORCE_UNLOAD;
1988           return True;
1989         }
1990
1991       if (! find_newest_sprite (mi, IMAGE))
1992         return False;  /* Too early */
1993
1994       ss->now = double_time();
1995
1996 # define BONK() do {                                  \
1997         if (ss->anim_state != MANUAL_BOX_ON &&        \
1998             ss->anim_state != MANUAL_BOX) {           \
1999           ss->anim_state = MANUAL_BOX_ON;             \
2000           tick_animation (mi);                        \
2001         }                                             \
2002         sp = find_newest_sprite (mi, BOX);            \
2003         if (!sp) abort() ;                            \
2004         sp->from = sp->current;                       \
2005         sp->to = sp->from;                            \
2006         sp->start_time = ss->now - sp->fade_duration; \
2007         ok = True;                                    \
2008       } while(0)
2009
2010       if (keysym == XK_Left || c == ',' || c == '<')
2011         {
2012           BONK();
2013           sp->to.x -= delta;
2014         }
2015       else if (keysym == XK_Right || c == '.' || c == '>')
2016         {
2017           BONK();
2018           sp->to.x += delta;
2019         }
2020       else if (keysym == XK_Down || c == '-')
2021         {
2022           BONK();
2023           sp->to.y -= delta;
2024         }
2025       else if (keysym == XK_Up || c == '=')
2026         {
2027           BONK();
2028           sp->to.y += delta, ok = True;
2029         }
2030       else if (keysym == XK_Prior || c == '+')
2031         {
2032           BONK();
2033           sp->to.w += delta;
2034           sp->to.h = sp->to.w * sp->from.w / sp->from.h;
2035         }
2036       else if (keysym == XK_Next || c == '_')
2037         {
2038           BONK();
2039           sp->to.w -= delta;
2040           sp->to.h = sp->to.w * sp->from.w / sp->from.h;
2041         }
2042       else if ((c == ' ' || c == '\t') && debug_p &&
2043                ss->anim_state == MANUAL_BOX)
2044         {
2045           BONK();     /* Null motion: just flash the current image. */
2046         }
2047       else if ((keysym == XK_Home || c == ' ' || c == '\t') &&
2048                ss->anim_state == MANUAL_BOX)
2049         {
2050           BONK();
2051           sp->to.x = 0.5;
2052           sp->to.y = 0.5;
2053           sp->to.w = 0.5;
2054           sp->to.h = 0.5;
2055         }
2056       else if ((c == '\r' || c == '\n' || c == 033) &&
2057                ss->anim_state == MANUAL_BOX)
2058         {
2059           BONK();
2060           ss->anim_state = BOX_MOVE;
2061           ss->anim_duration = 9999;
2062           ss->anim_start = ss->now - ss->anim_duration;
2063           fadeout_sprite (mi, sp);
2064           return True;
2065         }
2066       else
2067         return False;
2068
2069       if (! ok)
2070         return False;
2071
2072       /* Keep it on screen */
2073       if (sp->to.w > 1 - margin)
2074         {
2075           GLfloat r = sp->to.h / sp->to.w;
2076           sp->to.w = 1-margin;
2077           sp->to.h = (1-margin) * r;
2078         }
2079       if (sp->to.h > 1)
2080         {
2081           GLfloat r = sp->to.h / sp->to.w;
2082           sp->to.w = (1-margin) / r;
2083           sp->to.h = 1-margin;
2084         }
2085
2086       if (sp->to.x - sp->to.w/2 < margin)
2087         sp->to.x = sp->to.w/2 + margin;
2088       if (sp->to.y - sp->to.h/2 < margin)
2089         sp->to.y = sp->to.h/2 + margin;
2090
2091       if (sp->to.x + sp->to.w/2 >= 1 + margin)
2092         sp->to.x = 1 - (sp->to.w/2 + margin);
2093       if (sp->to.y + sp->to.h/2 >= 1 + margin)
2094         sp->to.y = 1 - (sp->to.h/2 + margin);
2095
2096       /* Now let's give a momentary glimpse of what the image would do. */
2097       if (debug_p)
2098         {
2099           sprite *img;
2100           int i;
2101
2102           /* Find the lingering image */
2103           /* img = find__sprite (mi, IMAGE); */
2104           for (i = 0; i < ss->nsprites; i++)
2105             {
2106               sprite *sp2 = ss->sprites[i];
2107               if (sp2->type == IMAGE &&
2108                   sp2->remain_p &&
2109                   (!img || 
2110                    (img->start_time < sp2->start_time &&
2111                     ss->now >= sp2->start_time + sp2->pause_duration)))
2112                 img = sp2;
2113             }
2114
2115           if (!img) abort();
2116           img = copy_sprite (mi, img);
2117           img->pause_duration = 0;
2118           img->fade_duration = 0.1 / speed;
2119           img->duration = 0.5 / speed;
2120           img->start_time = ss->now;
2121           img->remain_p = False;
2122           track_box_with_image (mi, sp, img);
2123           img->from = img->current = img->to;
2124         }
2125
2126       return True;
2127     }
2128   else if (event->xany.type == ButtonPress &&
2129            event->xbutton.button == Button1)
2130     {
2131       ss->button_down_p = 1;
2132       return True;
2133     }
2134   else if (event->xany.type == ButtonRelease &&
2135            event->xbutton.button == Button1)
2136     {
2137       ss->button_down_p = 0;
2138
2139       if (ss->anim_state == MANUAL_BOX)
2140         {
2141           sprite *sp = find_newest_sprite (mi, BOX);
2142           if (sp) fadeout_sprite (mi, sp);
2143           ss->anim_state = BOX_MOVE;
2144           ss->anim_duration = 9999;
2145           ss->anim_start = ss->now - ss->anim_duration;
2146         }
2147       else if (ss->anim_state == MANUAL_RETICLE)
2148         {
2149           sprite *sp = find_newest_sprite (mi, RETICLE);
2150           if (sp) fadeout_sprite (mi, sp);
2151           ss->anim_state = RETICLE_MOVE;
2152           ss->anim_duration = 9999;
2153           ss->anim_start = ss->now - ss->anim_duration;
2154         }
2155       return True;
2156     }
2157   else if (event->xany.type == MotionNotify &&
2158            ss->button_down_p &&
2159            (ss->anim_state == MANUAL_RETICLE ||
2160             ss->anim_state == RETICLE_MOVE))
2161     {
2162       sprite *sp = 0;
2163       double x = event->xmotion.x;
2164       double y = event->xmotion.y;
2165       double w = MI_WIDTH(mi);
2166       double h = MI_HEIGHT(mi);
2167
2168       adjust_for_device_rotation (&x, &y, &w, &h);
2169       x = x/w;
2170       y = 1-y/h;
2171
2172       if (ss->anim_state != MANUAL_RETICLE_ON &&
2173           ss->anim_state != MANUAL_RETICLE)
2174         {
2175           ss->anim_state = MANUAL_RETICLE_ON;
2176           tick_animation (mi);
2177         }
2178       sp = find_newest_sprite (mi, RETICLE);
2179       if (!sp) abort();
2180       sp->from = sp->current;
2181       sp->to = sp->from;
2182       sp->start_time = ss->now - sp->fade_duration;
2183       sp->remain_p = True;
2184
2185       sp->current.x = MIN (0.95, MAX (0.05, x));
2186       sp->current.y = MIN (0.95, MAX (0.05, y));
2187       sp->from = sp->to = sp->current;
2188
2189       /* Don't update the text sprite more often than once a second. */
2190       {
2191         sprite *sp2 = find_newest_sprite (mi, TEXT);
2192         if (!sp2 || sp2->start_time < ss->now-1)
2193           {
2194             fadeout_sprites (mi, TEXT);
2195             sp = push_text_sprite (mi, sp);
2196             sp->remain_p = True;
2197           }
2198       }
2199
2200       return True;
2201     }
2202   else if (event->xany.type == MotionNotify &&
2203            ss->button_down_p &&
2204            (ss->anim_state == MANUAL_BOX ||
2205             ss->anim_state == BOX_MOVE))
2206     {
2207       sprite *sp = 0;
2208       double x = event->xmotion.x;
2209       double y = event->xmotion.y;
2210       double w = MI_WIDTH(mi);
2211       double h = MI_HEIGHT(mi);
2212       double max;
2213       Bool ok = True;
2214
2215       adjust_for_device_rotation (&x, &y, &w, &h);
2216       x = x/w;
2217       y = 1-y/h;
2218
2219       BONK();
2220       max = (2 * (0.5 - MAX (fabs (sp->current.x - 0.5),
2221                              fabs (sp->current.y - 0.5)))
2222              * 0.95);
2223
2224       x = fabs (x - sp->current.x);
2225       y = fabs (y - sp->current.y);
2226
2227       if (x > y)
2228         sp->current.w = sp->current.h = MIN (max, MAX (0.05, 2*x));
2229       else
2230         sp->current.w = sp->current.h = MIN (max, MAX (0.05, 2*y));
2231       sp->from = sp->to = sp->current;
2232
2233       /* Don't update the text sprite more often than once a second. */
2234       {
2235         sprite *sp2 = find_newest_sprite (mi, TEXT);
2236         if (!sp2 || sp2->start_time < ss->now-1)
2237           {
2238             fadeout_sprites (mi, TEXT);
2239             sp = push_text_sprite (mi, sp);
2240             sp->remain_p = True;
2241           }
2242       }
2243
2244       return True;
2245     }
2246   else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
2247     {
2248       ss->anim_state = IMAGE_FORCE_UNLOAD;
2249       return True;
2250     }
2251 # undef BONK
2252
2253   return False;
2254 }
2255
2256
2257 ENTRYPOINT void
2258 reshape_esper (ModeInfo *mi, int width, int height)
2259 {
2260   GLfloat s;
2261
2262   glViewport (0, 0, width, height);
2263   glMatrixMode (GL_PROJECTION);
2264   glLoadIdentity();
2265   glRotatef (current_device_rotation(), 0, 0, 1);
2266   glMatrixMode (GL_MODELVIEW);
2267   glLoadIdentity();
2268
2269   s = 2;
2270
2271   if (debug_p)
2272     {
2273       s *= 0.75;
2274       if (s < 0.1) s = 0.1;
2275     }
2276
2277   glScalef (s, s, s);
2278   glTranslatef (-0.5, -0.5, 0);
2279
2280   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
2281
2282   /* Stretch each existing image to match new window aspect. */
2283   {
2284     esper_state *ss = &sss[MI_SCREEN(mi)];
2285     int i;
2286     for (i = 0; i < ss->nsprites; i++)
2287       {
2288         sprite *sp = ss->sprites[i];
2289         if (sp && sp->type == IMAGE && sp->img && sp->img->loaded_p)
2290           {
2291             GLfloat sp_asp  = sp->current.h / sp->current.w;
2292             GLfloat img_asp = (sp->img->geom.height /
2293                                (GLfloat) sp->img->geom.width);
2294             GLfloat new_win = (MI_WIDTH(mi) / (double) MI_HEIGHT(mi));
2295             GLfloat old_win = sp_asp / img_asp;
2296             GLfloat r = old_win / new_win;
2297             if (img_asp > 1)
2298               {
2299                 sp->from.h    /= r;
2300                 sp->current.h /= r;
2301                 sp->to.h      /= r;
2302               }
2303             else
2304               {
2305                 sp->from.w    *= r;
2306                 sp->current.w *= r;
2307                 sp->to.w      *= r;
2308               }
2309           }
2310       }
2311   }
2312 }
2313
2314
2315 static void
2316 parse_color (ModeInfo *mi, char *key, GLfloat color[4])
2317 {
2318   XColor xcolor;
2319   char *string = get_string_resource (mi->dpy, key, "EsperColor");
2320   if (!XParseColor (mi->dpy, mi->xgwa.colormap, string, &xcolor))
2321     {
2322       fprintf (stderr, "%s: unparsable color in %s: %s\n", progname,
2323                key, string);
2324       exit (1);
2325     }
2326
2327   color[0] = xcolor.red   / 65536.0;
2328   color[1] = xcolor.green / 65536.0;
2329   color[2] = xcolor.blue  / 65536.0;
2330   color[3] = 1;
2331 }
2332
2333
2334 ENTRYPOINT void
2335 init_esper (ModeInfo *mi)
2336 {
2337   int screen = MI_SCREEN(mi);
2338   esper_state *ss;
2339   int wire = MI_IS_WIREFRAME(mi);
2340   
2341   MI_INIT (mi, sss);
2342   ss = &sss[screen];
2343
2344   if ((ss->glx_context = init_GL(mi)) != NULL) {
2345     reshape_esper (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
2346   } else {
2347     MI_CLEARWINDOW(mi);
2348   }
2349
2350   parse_color (mi, "gridColor", ss->grid_color);
2351   parse_color (mi, "reticleColor", ss->reticle_color);
2352   parse_color (mi, "textColor", ss->text_color);
2353
2354   glDisable (GL_LIGHTING);
2355   glDisable (GL_DEPTH_TEST);
2356   glDepthMask (GL_FALSE);
2357   glEnable (GL_CULL_FACE);
2358   glCullFace (GL_BACK);
2359
2360   if (! wire)
2361     {
2362       glEnable (GL_TEXTURE_2D);
2363       glShadeModel (GL_SMOOTH);
2364       glEnable (GL_BLEND);
2365       glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2366     }
2367
2368   ss->font_data = load_texture_font (mi->dpy, "titleFont");
2369
2370   ss->now = double_time();
2371   ss->dawn_of_time = ss->now;
2372
2373   alloc_image (mi);
2374
2375   ss->anim_state = BLANK;
2376   ss->anim_start = 0;
2377   ss->anim_duration = 0;
2378 }
2379
2380
2381 ENTRYPOINT void
2382 draw_esper (ModeInfo *mi)
2383 {
2384   esper_state *ss = &sss[MI_SCREEN(mi)];
2385
2386   if (!ss->glx_context)
2387     return;
2388
2389   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(ss->glx_context));
2390
2391   mi->polygon_count = 0;
2392
2393   ss->now = double_time();
2394
2395   tick_sprites (mi);
2396   draw_sprites (mi);
2397   if (ss->now >= ss->anim_start + ss->anim_duration)
2398     tick_animation (mi);
2399
2400   if (mi->fps_p) do_fps (mi);
2401
2402   glFinish();
2403   glXSwapBuffers (MI_DISPLAY (mi), MI_WINDOW(mi));
2404 }
2405
2406 XSCREENSAVER_MODULE ("Esper", esper)
2407
2408 #endif /* USE_GL */