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