From http://www.jwz.org/xscreensaver/xscreensaver-5.38.tar.gz
[xscreensaver] / hacks / glx / splitflap.c
1 /* splitflap, Copyright (c) 2015 Jamie Zawinski <jwz@jwz.org>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10  *
11  * Draws a split-flap text display.
12  */
13
14 #define FLAP_FONT "-*-helvetica-bold-r-normal-*-*-1440-*-*-*-*-*-*"
15
16 #define DEFAULTS        "*delay:        20000       \n" \
17                         "*showFPS:      False       \n" \
18                         "*wireframe:    False       \n" \
19                         "*flapFont:   " FLAP_FONT  "\n" \
20                         "*frameColor:   #444444"   "\n" \
21                         "*caseColor:    #666666"   "\n" \
22                         "*discColor:    #888888"   "\n" \
23                         "*finColor:     #222222"   "\n" \
24                         "*textColor:    #FFFF00"   "\n" \
25                         "*multiSample:  True        \n" \
26                         "*program:      xscreensaver-text\n" \
27                         "*usePty:       False\n"
28
29 # define release_splitflap 0
30 #undef countof
31 #define countof(x) (sizeof((x))/sizeof((*x)))
32
33 #define DEF_SPEED       "1.0"
34 #define DEF_WIDTH       "22"
35 #define DEF_HEIGHT      "8"
36 #define DEF_SPIN        "XYZ"
37 #define DEF_WANDER      "True"
38 #define DEF_FACE_FRONT  "True"
39 #define DEF_MODE        "Text"
40
41 #include "xlockmore.h"
42
43 #include <ctype.h>
44
45 #ifdef USE_GL /* whole file */
46
47 #include "gltrackball.h"
48 #include "rotator.h"
49 #include "xpm-ximage.h"
50 #include "utf8wc.h"
51 #include "textclient.h"
52 #include "texfont.h"
53 #include "gllist.h"
54
55 extern const struct gllist
56   *splitflap_obj_box_quarter_frame, *splitflap_obj_disc_quarter,
57   *splitflap_obj_fin_edge_half, *splitflap_obj_fin_face_half;
58 static struct gllist *splitflap_obj_outer_frame = 0;
59
60 static const struct gllist * const *all_objs[] = {
61   &splitflap_obj_box_quarter_frame, &splitflap_obj_disc_quarter, 
62   &splitflap_obj_fin_edge_half, &splitflap_obj_fin_face_half,
63   (const struct gllist * const *) &splitflap_obj_outer_frame
64 };
65
66 #define SPLITFLAP_QUARTER_FRAME 0
67 #define SPLITFLAP_DISC_QUARTER  1
68 #define SPLITFLAP_FIN_EDGE_HALF 2
69 #define SPLITFLAP_FIN_FACE_HALF 3
70 #define SPLITFLAP_OUTER_FRAME   4
71
72 #define COLON_WIDTH 0.5
73
74 typedef struct {
75   int target_index;             /* desired character */
76   double current_index;         /* currently displayed, fractional */
77   GLfloat sticky;               /* bottom fin doesn't fall all the way */
78   int missing;                  /* which fin has snapped off, or -1 */
79   const char * const *spool;    /* chars available for display */
80   int spool_size;               /* how many fins on the spool */
81 } flapper;
82
83 typedef struct {
84   const char *text;
85   GLuint texid;
86   XCharStruct metrics;
87   int tex_width, tex_height;
88 } texinfo;
89
90 typedef struct {
91   GLXContext *glx_context;
92   rotator *rot, *rot2;
93   trackball_state *trackball;
94   Bool button_down_p;
95   Bool spinx, spiny, spinz;
96
97   texinfo *texinfo;
98   int texinfo_size;
99
100   GLuint *dlists;
101   GLfloat component_colors[countof(all_objs)][4];
102   GLfloat text_color[4];
103
104   flapper *flappers;  /* grid_width * grid_height */
105
106   texture_font_data *font_data;
107   int ascent, descent;
108
109   text_data *tc;
110   unsigned char text[5];
111   int linger;
112   int clock_p;
113   Bool first_time_p;
114
115 } splitflap_configuration;
116
117 static const char * const digit_s1_spool[] = { " ", "1" };
118 static const char * const digit_01_spool[] = { "0", "1" };
119 static const char * const ap_spool[]       = { "A", "P" };
120 static const char * const m_spool[]        = { "M" };
121 static const char * const digit_05_spool[] = { "0", "1", "2", "3", "4", "5" };
122 static const char * const digit_spool[] = {
123   "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"
124 };
125
126 static const char * const ascii_spool[] = {
127   " ", "!", "\"", "#", "$", "%", "&", "'",
128   "(", ")", "*", "+", ",", "-", ".", "/",
129   "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
130   ":", ";", "<", "=", ">", "?", "@",
131   "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
132   "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
133   "[", "\\", "]", "^", "_", "`", "{", "|", "}", "~", 
134 };
135
136
137 /* If we include these, the flappers just take too long. It's boring. */
138 static const char * const latin1_spool[] = {
139   " ", "!", "\"", "#", "$", "%", "&", "'",
140   "(", ")", "*", "+", ",", "-", ".", "/",
141   "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
142   ":", ";", "<", "=", ">", "?", "@",
143   "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
144   "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
145   "[", "\\", "]", "^", "_", "`", "{", "|", "}", "~", 
146
147   "\302\241", "\302\242", "\302\243", "\302\245",
148   "\302\247", "\302\251", "\302\265", "\302\266",
149
150   "\303\200", "\303\201", "\303\202", "\303\203",
151   "\303\204", "\303\205", "\303\206", "\303\207",
152   "\303\210", "\303\211", "\303\212", "\303\213",
153   "\303\214", "\303\215", "\303\216", "\303\217",
154   "\303\220", "\303\221", "\303\222", "\303\223",
155   "\303\224", "\303\225", "\303\226", "\303\230",
156   "\303\231", "\303\232", "\303\233", "\303\234",
157   "\303\235", "\303\236", "\303\237", "\303\267",
158 };
159
160
161 static splitflap_configuration *bps = NULL;
162
163 static GLfloat speed;
164 static int grid_width, grid_height;
165 static char *do_spin;
166 static Bool do_wander;
167 static Bool face_front_p;
168 static char *mode_str;
169
170 static XrmOptionDescRec opts[] = {
171   { "-speed",   ".speed",     XrmoptionSepArg, 0 },
172   { "-width",   ".width",     XrmoptionSepArg, 0 },
173   { "-height",  ".height",    XrmoptionSepArg, 0 },
174   { "-spin",    ".spin",      XrmoptionSepArg, 0 },
175   { "+spin",    ".spin",      XrmoptionNoArg, "" },
176   { "-wander",  ".wander",    XrmoptionNoArg, "True" },
177   { "+wander",  ".wander",    XrmoptionNoArg, "False" },
178   { "-front",   ".faceFront", XrmoptionNoArg, "True" },
179   { "+front",   ".faceFront", XrmoptionNoArg, "False" },
180   { "-mode",    ".mode",      XrmoptionSepArg, 0 },
181 };
182
183 static argtype vars[] = {
184   {&speed,       "speed",      "Speed",     DEF_SPEED,      t_Float},
185   {&grid_width,  "width",      "Width",     DEF_WIDTH,      t_Int},
186   {&grid_height, "height",     "Height",    DEF_HEIGHT,     t_Int},
187   {&do_spin,      "spin",      "Spin",      DEF_SPIN,       t_String},
188   {&do_wander,    "wander",    "Wander",    DEF_WANDER,     t_Bool},
189   {&face_front_p, "faceFront", "FaceFront", DEF_FACE_FRONT, t_Bool},
190   {&mode_str,     "mode",       "Mode",     DEF_MODE,       t_String},
191 };
192
193 ENTRYPOINT ModeSpecOpt splitflap_opts = {
194   countof(opts), opts, countof(vars), vars, NULL};
195
196
197 /* Window management, etc
198  */
199 ENTRYPOINT void
200 reshape_splitflap (ModeInfo *mi, int width, int height)
201 {
202   GLfloat h = (GLfloat) height / (GLfloat) width;
203
204   glViewport (0, 0, (GLint) width, (GLint) height);
205
206   glMatrixMode(GL_PROJECTION);
207   glLoadIdentity();
208   gluPerspective (40.0, 1/h, 0.5, 25);
209
210   glMatrixMode(GL_MODELVIEW);
211   glLoadIdentity();
212   gluLookAt( 0, 0, 3,  /* 10x lower than traditional, for better depth rez */
213              0, 0, 0,
214              0, 1, 0);
215
216 # ifdef HAVE_MOBILE     /* Keep it the same relative size when rotated. */
217   {
218     int o = (int) current_device_rotation();
219     if (o != 0 && o != 180 && o != -180)
220       glScalef (h, h, h);
221   }
222 # endif
223
224   glClear(GL_COLOR_BUFFER_BIT);
225 }
226
227
228 ENTRYPOINT Bool
229 splitflap_handle_event (ModeInfo *mi, XEvent *event)
230 {
231   splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
232
233   if (gltrackball_event_handler (event, bp->trackball,
234                                  MI_WIDTH (mi), MI_HEIGHT (mi),
235                                  &bp->button_down_p))
236     return True;
237
238   return False;
239 }
240
241
242 static void
243 init_textures (ModeInfo *mi)
244 {
245   splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
246   int i;
247   const char * const *spool = latin1_spool;
248   int max = countof(latin1_spool);
249
250   bp->texinfo = (texinfo *) calloc (max+1, sizeof(*bp->texinfo));
251   texture_string_metrics (bp->font_data, "", 0, &bp->ascent, &bp->descent);
252
253   for (i = 0; i < max; i++)
254     {
255       texinfo *ti = &bp->texinfo[i];
256       glGenTextures (1, &ti->texid);
257       glBindTexture (GL_TEXTURE_2D, ti->texid);
258
259       ti->text = spool[i];
260
261       /* fprintf(stderr, "%d \\%03o\\%03o %s\n", i,
262               (unsigned char) ti->text[0],
263               (unsigned char) ti->text[1],
264               ti->text); */
265
266       string_to_texture (bp->font_data, ti->text, &ti->metrics,
267                          &ti->tex_width, &ti->tex_height);
268     }
269   bp->texinfo_size = i;
270
271   glBindTexture (GL_TEXTURE_2D, 0);
272 }
273
274
275 static void
276 parse_color (ModeInfo *mi, char *key, GLfloat color[4])
277 {
278   XColor xcolor;
279   char *string = get_string_resource (mi->dpy, key, "Color");
280   if (!XParseColor (mi->dpy, mi->xgwa.colormap, string, &xcolor))
281     {
282       fprintf (stderr, "%s: unparsable color in %s: %s\n", progname,
283                key, string);
284       exit (1);
285     }
286
287   color[0] = xcolor.red   / 65536.0;
288   color[1] = xcolor.green / 65536.0;
289   color[2] = xcolor.blue  / 65536.0;
290   color[3] = 1;
291 }
292
293
294 static int draw_outer_frame (ModeInfo *mi);
295
296 ENTRYPOINT void 
297 init_splitflap (ModeInfo *mi)
298 {
299   splitflap_configuration *bp;
300   int wire = MI_IS_WIREFRAME(mi);
301   int i;
302   MI_INIT (mi, bps);
303
304   bp = &bps[MI_SCREEN(mi)];
305   bp->glx_context = init_GL(mi);
306   reshape_splitflap (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
307
308   bp->first_time_p = True;
309
310   if (!mode_str || !*mode_str || !strcasecmp(mode_str, "text"))
311     {
312       bp->clock_p = 0;
313     }
314   else if (!strcasecmp (mode_str, "clock") ||
315            !strcasecmp (mode_str, "clock12"))
316     {
317       bp->clock_p = 12;
318       grid_width  = 8;
319       grid_height = 1;
320     }
321   else if (!strcasecmp (mode_str, "clock24"))
322     {
323       bp->clock_p = 24;
324       grid_width  = 6;
325       grid_height = 1;
326     }
327   else
328     {
329       fprintf (stderr,
330            "%s: `mode' must be text, clock12 or clock24: not `%s'\n",
331                progname, mode_str);
332       exit (1);
333     }
334
335   if (! bp->clock_p)
336     {
337       bp->tc = textclient_open (MI_DISPLAY (mi));
338       bp->text[0] = 0;
339
340       if (grid_width > 10)
341         textclient_reshape (bp->tc, 
342                             grid_width, grid_height,
343                             grid_width, grid_height,
344                             0);
345     }
346
347   if (bp->clock_p)
348     speed /= 4;
349
350   glShadeModel(GL_SMOOTH);
351
352   glEnable(GL_DEPTH_TEST);
353   glEnable(GL_NORMALIZE);
354   glEnable(GL_CULL_FACE);
355
356   if (!wire)
357     {
358       GLfloat pos[4] = {0.4, 0.2, 0.4, 0.0};
359 /*      GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};*/
360       GLfloat amb[4] = {0.2, 0.2, 0.2, 1.0};
361       GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
362       GLfloat spc[4] = {1.0, 1.0, 1.0, 1.0};
363
364       glEnable(GL_LIGHTING);
365       glEnable(GL_LIGHT0);
366       glEnable(GL_DEPTH_TEST);
367       glEnable(GL_CULL_FACE);
368
369       glLightfv(GL_LIGHT0, GL_POSITION, pos);
370       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
371       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
372       glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
373
374       glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
375     }
376
377
378   {
379     double spin_speed   = 0.5;
380     double wander_speed = 0.005;
381     double tilt_speed   = 0.001;
382     double spin_accel   = 0.5;
383
384     char *s = do_spin;
385     while (*s)
386       {
387         if      (*s == 'x' || *s == 'X') bp->spinx = True;
388         else if (*s == 'y' || *s == 'Y') bp->spiny = True;
389         else if (*s == 'z' || *s == 'Z') bp->spinz = True;
390         else if (*s == '0') ;
391         else
392           {
393             fprintf (stderr,
394          "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
395                      progname, do_spin);
396             exit (1);
397           }
398         s++;
399       }
400
401     bp->rot = make_rotator (bp->spinx ? spin_speed : 0,
402                             bp->spiny ? spin_speed : 0,
403                             bp->spinz ? spin_speed : 0,
404                             spin_accel,
405                             do_wander ? wander_speed : 0,
406                             False);
407     bp->rot2 = (face_front_p
408                 ? make_rotator (0, 0, 0, 0, tilt_speed, True)
409                 : 0);
410     bp->trackball = gltrackball_init (False);
411   }
412
413   bp->dlists = (GLuint *) calloc (countof(all_objs)+1, sizeof(GLuint));
414   for (i = 0; i < countof(all_objs); i++)
415     bp->dlists[i] = glGenLists (1);
416
417   parse_color (mi, "textColor", bp->text_color);
418   for (i = 0; i < countof(all_objs); i++)
419     {
420       const struct gllist *gll = *all_objs[i];
421       char *key = 0;
422       GLfloat spec[4] = {0.4, 0.4, 0.4, 1.0};
423       GLfloat shiny = 80; /* 0-128 */
424
425       glNewList (bp->dlists[i], GL_COMPILE);
426
427       glMatrixMode(GL_MODELVIEW);
428       glPushMatrix();
429       glMatrixMode(GL_TEXTURE);
430       glPushMatrix();
431       glMatrixMode(GL_MODELVIEW);
432
433       glRotatef (-90, 1, 0, 0);
434
435       glBindTexture (GL_TEXTURE_2D, 0);
436
437       switch (i) {
438       case SPLITFLAP_QUARTER_FRAME:
439         key = "frameColor";
440         break;
441       case SPLITFLAP_OUTER_FRAME:
442         key = "caseColor";
443         break;
444       case SPLITFLAP_DISC_QUARTER:
445         key = (wire ? "frameColor" : "discColor");
446         break;
447       case SPLITFLAP_FIN_EDGE_HALF:
448       case SPLITFLAP_FIN_FACE_HALF:
449         key = "finColor";
450         break;
451       default:
452         abort();
453       }
454
455       parse_color (mi, key, bp->component_colors[i]);
456
457       if (wire && i == SPLITFLAP_FIN_EDGE_HALF)
458         bp->component_colors[i][0] = 
459         bp->component_colors[i][1] = 
460         bp->component_colors[i][2] = 0.7;
461
462       glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR,  spec);
463       glMaterialf  (GL_FRONT_AND_BACK, GL_SHININESS, shiny);
464
465       switch (i) {
466       case SPLITFLAP_OUTER_FRAME:
467         if (! splitflap_obj_outer_frame)
468           splitflap_obj_outer_frame =
469             (struct gllist *) calloc (1, sizeof(*splitflap_obj_outer_frame));
470         splitflap_obj_outer_frame->points = draw_outer_frame(mi);
471         break;
472       default:
473         renderList (gll, wire);
474         break;
475       }
476
477       glMatrixMode(GL_TEXTURE);
478       glPopMatrix();
479       glMatrixMode(GL_MODELVIEW);
480       glPopMatrix();
481
482       glEndList ();
483     }
484
485   if (grid_width < 1)  grid_width  = 1;
486   if (grid_height < 1) grid_height = 1;
487   bp->flappers = (flapper *) calloc (grid_width * grid_height,
488                                      sizeof (flapper));
489
490   for (i = 0; i < grid_width * grid_height; i++)
491     {
492       flapper *f = &bp->flappers[i];
493
494       if (!bp->clock_p)
495         {
496           f->spool = ascii_spool;
497           f->spool_size = countof (ascii_spool);
498         }
499       else
500         {
501           switch (i) {
502           case 0:
503             if (bp->clock_p == 12)
504               {
505                 f->spool = digit_s1_spool;
506                 f->spool_size = countof (digit_s1_spool);
507               }
508             else
509               {
510                 f->spool = digit_01_spool;
511                 f->spool_size = countof (digit_01_spool);
512               }
513             break;
514           case 1: case 3: case 5:
515             f->spool = digit_spool;
516             f->spool_size = countof (digit_spool);
517             break;
518           case 2: case 4:
519             f->spool = digit_05_spool;
520             f->spool_size = countof (digit_05_spool);
521             break;
522           case 6:
523             f->spool = ap_spool;
524             f->spool_size = countof (ap_spool);
525             break;
526           case 7:
527             f->spool = m_spool;
528             f->spool_size = countof (m_spool);
529             break;
530           default:
531             abort();
532           }
533         }
534
535       f->target_index = random() % f->spool_size;
536       /* f->target_index = 0; */
537       f->current_index = f->target_index;
538       f->missing = (((random() % 10) == 0)
539                     ? (random() % f->spool_size)
540                     : -1);
541     }
542
543   bp->font_data = load_texture_font (mi->dpy, "flapFont");
544   init_textures (mi);
545
546   reshape_splitflap (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
547 }
548
549
550 static int
551 draw_component (ModeInfo *mi, int i)
552 {
553   splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
554   glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE,
555                 bp->component_colors[i]);
556   glCallList (bp->dlists[i]);
557   return (*all_objs[i])->points / 3;
558 }
559
560
561 static int
562 draw_frame_quarter (ModeInfo *mi, flapper *f)
563 {
564   int count = 0;
565   glPushMatrix();
566   count += draw_component (mi, SPLITFLAP_QUARTER_FRAME);
567   glPopMatrix();
568   return count;
569 }
570
571 static int
572 draw_disc_quarter (ModeInfo *mi, flapper *f)
573 {
574   int count = 0;
575   glPushMatrix();
576   count += draw_component (mi, SPLITFLAP_DISC_QUARTER);
577   glPopMatrix();
578   return count;
579 }
580
581 static int
582 draw_fin_edge_half (ModeInfo *mi, flapper *f)
583 {
584   int count = 0;
585   glPushMatrix();
586   count += draw_component (mi, SPLITFLAP_FIN_EDGE_HALF);
587   glPopMatrix();
588   return count;
589 }
590
591 static int
592 draw_fin_face_half (ModeInfo *mi, flapper *f)
593 {
594   int count = 0;
595   if (MI_IS_WIREFRAME(mi)) return 0;
596   glPushMatrix();
597   count += draw_component (mi, SPLITFLAP_FIN_FACE_HALF);
598   glPopMatrix();
599   return count;
600 }
601
602
603 static int
604 draw_frame (ModeInfo *mi, flapper *f)
605 {
606   int count = 0;
607
608   glPushMatrix();
609
610   glFrontFace (GL_CCW);
611   count += draw_frame_quarter (mi, f);
612   count += draw_disc_quarter (mi, f);
613
614   glScalef (-1, 1, 1);
615   glFrontFace (GL_CW);
616   count += draw_frame_quarter (mi, f);
617   count += draw_disc_quarter (mi, f);
618
619   glScalef ( 1, -1, 1);
620   glFrontFace (GL_CCW);
621   count += draw_frame_quarter (mi, f);
622   count += draw_disc_quarter (mi, f);
623
624   glScalef (-1, 1, 1);
625   glFrontFace (GL_CW);
626   count += draw_frame_quarter (mi, f);
627   count += draw_disc_quarter (mi, f);
628
629   glPopMatrix();
630   return count;
631 }
632
633
634 static void
635 draw_fin_text_quad (ModeInfo *mi, flapper *f, int index, Bool top_p)
636 {
637   int wire = MI_IS_WIREFRAME(mi);
638   splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
639
640   /* 15  / is weird
641      27  ; descends too far
642      32  @ is too wide
643      59  [ descends too far
644      79  A^ is taller than the font
645      89  I` is weird
646    */
647
648   GLfloat z = 0.035;    /* Lifted off the surface by this distance */
649   GLfloat bot = 0.013;  /* Distance away from the mid gutter */
650   GLfloat scale = 1.8;  /* Scale to fill the panel */
651
652   int lh = bp->ascent + bp->descent;
653   texinfo *ti;
654   GLfloat qx0, qy0, qx1, qy1;
655   GLfloat tx0, ty0, tx1, ty1;
656   XCharStruct overall;
657   int tex_width, tex_height;
658   int i;
659
660   for (i = 0; i < bp->texinfo_size; i++)
661     {
662       ti = &bp->texinfo[i];
663       if (!strcmp (f->spool[index], ti->text))
664         break;
665     }
666   if (i >= bp->texinfo_size) abort();
667
668   overall = ti->metrics;
669   tex_width  = ti->tex_width;
670   tex_height = ti->tex_height;
671
672   if (bp->ascent < overall.ascent)
673     /* WTF! &Aacute; has a higher ascent than the font itself!
674        Scale it down so that it doesn't overlap the fin. */
675     scale *= bp->ascent / (GLfloat) overall.ascent * 0.98;
676
677   glPushMatrix();
678
679   glNormal3f (0, 0, 1);
680   glFrontFace (top_p ? GL_CCW : GL_CW);
681
682   if (! wire)
683     {
684       glBindTexture (GL_TEXTURE_2D, ti->texid);
685       enable_texture_string_parameters();
686     }
687
688   glTranslatef (0, 0, z);               /* Move to just above the surface */
689   glScalef (1.0 / lh, 1.0 / lh, 1);     /* Scale to font pixel coordinates */
690   glScalef (scale, scale, 1);           /* Fill the panel with the font */
691
692   if (!top_p)
693     {
694       glRotatef (180, 0, 0, 1);
695     }
696
697   /* Position the XCharStruct origin at 0,0 in the scene. */
698   qx0 = -overall.lbearing;
699   qy0 = -overall.descent;
700   qx1 =  overall.rbearing;
701   qy1 =  overall.ascent;
702
703   /* Center horizontally. */
704   qx0 -= (overall.rbearing - overall.lbearing) / 2.0;
705   qx1 -= (overall.rbearing - overall.lbearing) / 2.0;
706
707
708   /* Move origin to below font descenders. */
709   qy0 += bp->descent;
710   qy1 += bp->descent;
711
712   /* Center vertically. */
713   qy0 -= (bp->ascent + bp->descent) / 2.0;
714   qy1 -= (bp->ascent + bp->descent) / 2.0;
715
716   /* Move the descenders down a bit, if there's room.
717      This means that weirdos like [ and $ might not be on the baseline.
718      #### This looks good with X11 fonts but bad with MacOS fonts.  WTF?
719    */
720 #ifndef HAVE_COCOA
721   {
722     GLfloat off = bp->descent / 3.0;
723     GLfloat max = bp->descent - off;
724     if (overall.descent > max)
725       off = max - overall.descent;
726     if (off < 0)
727       off = 0;
728     qy0 -= off;
729     qy1 -= off;
730   }
731 # endif /* !HAVE_COCOA */
732
733   /* Attach the texture to the quad. */
734   tx0 = 0;
735   ty1 = 0;
736   tx1 = (overall.rbearing - overall.lbearing) / (GLfloat) tex_width;
737   ty0 = (overall.ascent   + overall.descent)  / (GLfloat) tex_height;
738
739   /* Convert from font ascent/descent to character ascent/descent. */
740
741   /* Flip texture horizontally on bottom panel. */
742   if (!top_p)
743     {
744       GLfloat s = tx0;
745       tx0 = tx1;
746       tx1 = s;
747     }
748
749
750   /* Cut the character in half, truncating just above the split line. */
751   {
752     GLfloat oqy0 = qy0;
753     GLfloat oqy1 = qy1;
754     GLfloat r0, r1;
755
756     bot *= lh * scale;
757
758     if (top_p)
759       {
760         if (qy0 < bot)
761           qy0 = bot;
762       }
763     else
764       {
765         if (qy1 > -bot)
766           qy1 = -bot;
767       }
768
769     r0 = (qy0 - oqy0) / (oqy1 - oqy0);
770     r1 = (qy1 - oqy1) / (oqy1 - oqy0);
771     ty0 -= r0 * (ty0 - ty1);
772     ty1 -= r1 * (ty0 - ty1);
773   }
774
775   glColor4fv (bp->text_color);
776   glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
777   glTexCoord2f (tx0, ty0); glVertex3f (qx0, qy0, 0);
778   glTexCoord2f (tx1, ty0); glVertex3f (qx1, qy0, 0);
779   glTexCoord2f (tx1, ty1); glVertex3f (qx1, qy1, 0);
780   glTexCoord2f (tx0, ty1); glVertex3f (qx0, qy1, 0);
781   glEnd();
782
783   glPopMatrix();
784
785   if (! wire)
786     {
787       glDisable (GL_BLEND);
788       glEnable (GL_LIGHTING);
789       glDisable (GL_TEXTURE_2D);
790     }
791 }
792
793
794 static int
795 draw_fin (ModeInfo *mi, flapper *f, int front_index, int back_index,
796           Bool text_p)
797 {
798   int count = 0;
799
800   glPushMatrix();
801
802   glFrontFace (GL_CCW);
803
804   if (! text_p)
805     count += draw_fin_edge_half (mi, f);
806
807   if (front_index >= 0)
808     {
809       if (text_p)
810         {
811           draw_fin_text_quad (mi, f, front_index, True);
812           count++;
813         }
814       else
815         count += draw_fin_face_half (mi, f);
816     }
817
818   glScalef (-1, 1, 1);
819   if (! text_p)
820     {
821       glFrontFace (GL_CW);
822       count += draw_fin_edge_half (mi, f);
823       if (front_index >= 0)
824         count += draw_fin_face_half (mi, f);
825     }
826
827   if (back_index >= 0)
828     {
829       glRotatef (180, 0, 1, 0);
830       if (text_p)
831         {
832           draw_fin_text_quad (mi, f, back_index, False);
833           count++;
834         }
835       else
836         {
837           count += draw_fin_face_half (mi, f);
838           glScalef (-1, 1, 1);
839           glFrontFace (GL_CCW);
840           count += draw_fin_face_half (mi, f);
841         }
842     }
843
844   glPopMatrix();
845   return count;
846 }
847
848
849 /* The case holding the grid of flappers.
850  */
851 static int
852 draw_outer_frame (ModeInfo *mi)
853 {
854   splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
855   int count = 0;
856   GLfloat w = grid_width;
857   GLfloat h = grid_height;
858   GLfloat d = 1;
859
860   if (bp->clock_p == 12)
861     w += COLON_WIDTH * 3;
862   else if (bp->clock_p == 24)
863     w += COLON_WIDTH * 2;
864
865   w += 0.2;
866   h += 0.2;
867
868   if (bp->clock_p) w += 0.25;
869   if (w > 3) w += 0.5;
870   if (h > 3) h += 0.5;
871
872   if (MI_IS_WIREFRAME(mi))
873     return 0;
874
875   glFrontFace (GL_CCW);
876   glPushMatrix();
877   glTranslatef (0, 1.03, 0);
878
879   glBegin (GL_QUADS);
880
881   glNormal3f ( 0,  1,  0);      /* back */
882   glVertex3f (-w,  d,  h);
883   glVertex3f ( w,  d,  h);
884   glVertex3f ( w,  d, -h);
885   glVertex3f (-w,  d, -h);
886   count++;
887
888   glNormal3f ( 0, -1,  0);      /* front */
889   glVertex3f (-w, -d, -h);
890   glVertex3f ( w, -d, -h);
891   glVertex3f ( w, -d,  h);
892   glVertex3f (-w, -d,  h);
893   count++;
894
895   glNormal3f ( 0, 0,   1);      /* top */
896   glVertex3f (-w, -d,  h);
897   glVertex3f ( w, -d,  h);
898   glVertex3f ( w,  d,  h);
899   glVertex3f (-w,  d,  h);
900   count++;
901
902   glNormal3f ( 0,  0, -1);      /* bottom */
903   glVertex3f (-w,  d, -h);
904   glVertex3f ( w,  d, -h);
905   glVertex3f ( w, -d, -h);
906   glVertex3f (-w, -d, -h);
907   count++;
908
909   glNormal3f ( 1,  0,  0);      /* left */
910   glVertex3f ( w, -d,  h);
911   glVertex3f ( w, -d, -h);
912   glVertex3f ( w,  d, -h);
913   glVertex3f ( w,  d,  h);
914   count++;
915
916   glNormal3f (-1,  0,  0);      /* right */
917   glVertex3f (-w, -d, -h);
918   glVertex3f (-w, -d,  h);
919   glVertex3f (-w,  d,  h);
920   glVertex3f (-w,  d, -h);
921   count++;
922
923   glEnd();
924   glPopMatrix();
925
926   return count;
927 }
928
929
930 static void
931 tick_flapper (ModeInfo *mi, flapper *f)
932 {
933   splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
934   double prev = f->current_index;
935   Bool wrapped_p = False;
936
937   if (bp->button_down_p) return;
938   if (f->current_index == f->target_index)
939     return;
940
941   f->current_index += speed * 0.35;             /* turn the crank */
942
943   while (f->current_index > f->spool_size)
944     {
945       f->current_index -= f->spool_size;
946       wrapped_p = True;
947     }
948
949   if (f->current_index < 0) abort();
950
951   if ((prev < f->target_index || wrapped_p) &&
952       f->current_index > f->target_index)       /* just overshot */
953     f->current_index = f->target_index;
954 }
955
956
957 #define MOD(M,N) (((M)+(N)) % (N))  /* Works with negatives */
958
959 static int
960 draw_flapper (ModeInfo *mi, flapper *f, Bool text_p)
961 {
962   int prev_index = floor (f->current_index);
963   int next_index = MOD (prev_index+1, f->spool_size);
964   int count = 0;
965   GLfloat epsilon = 0.02;
966   GLfloat r = f->current_index - prev_index;
967   Bool moving_p = (r > 0 && r < 1);
968   GLfloat sticky = f->sticky;
969
970   if (f->missing >= 0)
971     sticky = 0;
972
973   if (f->missing >= 0 &&
974       MOD (prev_index, f->spool_size) == f->missing)
975     {
976       moving_p = False;
977       sticky = 0;
978     }
979
980   if (!moving_p)
981     next_index = prev_index;
982
983   if (! text_p)
984     count += draw_frame (mi, f);
985
986   /* Top flap, flat: top half of target char */
987   if (!moving_p || !text_p || r > epsilon)
988     {
989       int p2 = next_index;
990
991       if (p2 == f->missing)
992         p2 = MOD (p2+1, f->spool_size);
993
994       count += draw_fin (mi, f, p2, -1, text_p);
995     }
996
997   /* Bottom flap, flat: bottom half of prev char */
998   if (!moving_p || !text_p || r < 1 - epsilon)
999     {
1000       int p2 = prev_index;
1001
1002       if (!moving_p && sticky)
1003         p2 = MOD (p2-1, f->spool_size);
1004
1005       if (f->missing >= 0 &&
1006           p2 == MOD (f->missing+1, f->spool_size))
1007         p2 = MOD (p2-1, f->spool_size);
1008
1009       glPushMatrix();
1010       glRotatef (180, 1, 0, 0);
1011       count += draw_fin (mi, f, -1, p2, text_p);
1012       glPopMatrix();
1013     }
1014
1015   /* Moving flap, front: top half of prev char */
1016   /* Moving flap, back: bottom half of target char */
1017   if (moving_p || sticky)
1018     {
1019       if (!moving_p)
1020         r = 1.0;
1021       if (sticky && r > 1 - sticky)
1022         r = 1 - sticky;
1023       glPushMatrix();
1024       glRotatef (r * 180, 1, 0, 0);
1025       count += draw_fin (mi, f, prev_index, next_index, text_p);
1026       glPopMatrix();
1027     }
1028
1029   return count;
1030 }
1031
1032
1033 static int
1034 draw_colon (ModeInfo *mi)
1035 {
1036   splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
1037   GLfloat s = 1.0 / (bp->ascent + bp->descent);
1038   GLfloat z = 0.01;
1039   int count = 0;
1040   XCharStruct m;
1041
1042   texture_string_metrics (bp->font_data, ":", &m, 0, 0);
1043
1044   s *= 2;
1045
1046   glPushMatrix();
1047
1048   glTranslatef (-(1 + COLON_WIDTH), 0, 0);
1049   glScalef (s, s, 1);
1050
1051   glTranslatef (-m.lbearing - (m.rbearing - m.lbearing)/2,
1052                 -(m.ascent + m.descent) / 2,
1053                 0);
1054
1055   glEnable (GL_TEXTURE_2D);
1056
1057   /* draw the text five times, to give it a border. */
1058   {
1059     const XPoint offsets[] = {{ -1, -1 },
1060                               { -1,  1 },
1061                               {  1,  1 },
1062                               {  1, -1 },
1063                               {  0,  0 }};
1064     int i;
1065     GLfloat n = 1.5;
1066
1067     glColor3f (0, 0, 0);
1068     for (i = 0; i < countof(offsets); i++)
1069       {
1070         glPushMatrix();
1071         if (offsets[i].x == 0)
1072           {
1073             glColor4fv (bp->text_color);
1074             glTranslatef (0, 0, z * 2);
1075           }
1076         glTranslatef (n * offsets[i].x, n * offsets[i].y, 0);
1077         print_texture_string (bp->font_data, ":");
1078         count++;
1079         glPopMatrix();
1080       }
1081   }
1082
1083   glPopMatrix();
1084
1085   return count;
1086 }
1087
1088
1089 /* Reads and returns a single Unicode character from the text client.
1090  */
1091 static unsigned long
1092 read_unicode (ModeInfo *mi)
1093 {
1094   splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
1095   const unsigned char *end = bp->text + sizeof(bp->text) - 1;  /* 4 bytes */
1096   unsigned long uc = 0;
1097   long L;
1098   int i;
1099
1100   if (bp->clock_p || !bp->tc) abort();
1101
1102   /* Fill the buffer with available input.
1103    */
1104   i = strlen ((char *) bp->text);
1105   while (i < (end - bp->text))
1106     {
1107       int c = textclient_getc (bp->tc);
1108       if (c <= 0) break;
1109       bp->text[i++] = (char) c;
1110       bp->text[i] = 0;
1111     }
1112
1113   /* Pop 1-4 bytes from the front of the buffer and extract a UTF8 character.
1114    */
1115   L = utf8_decode (bp->text, i, &uc);
1116   if (L)
1117     {
1118       int j = end - bp->text - L;
1119       memmove (bp->text, bp->text + L, j);
1120       bp->text[j] = 0;
1121     }
1122   else
1123     uc = 0;
1124
1125   return uc;
1126 }
1127
1128
1129 /* Given a Unicode character, finds the corresponding index on the spool,
1130    if any. Returns 0 if not found.
1131  */
1132 static int
1133 find_index (ModeInfo *mi, flapper *f, long uc)
1134 {
1135   char string[5];
1136   int L = utf8_encode (uc, string, sizeof(string) - 1);
1137   int i;
1138   if (L <= 0) return 0;
1139   string[L] = 0;
1140   for (i = 0; i < f->spool_size; i++)
1141     {
1142       if (!strcmp (string, f->spool[i]))
1143         return i;
1144     }
1145   return 0;
1146 }
1147
1148
1149 /* Read input from the text client and populate the spool with it.
1150  */
1151 static void
1152 fill_targets (ModeInfo *mi)
1153 {
1154   splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
1155   int x, y;
1156   Bool cls_p = False;
1157
1158   if (bp->clock_p)
1159     {
1160       char buf[80];
1161       time_t now = time ((time_t *) 0);
1162         struct tm *tm = localtime (&now);
1163       const char *fmt = (bp->clock_p == 24
1164                          ? "%H%M%S"
1165                          : "%I%M%S%p");
1166       int i;
1167       strftime (buf, sizeof(buf)-1, fmt, tm);
1168       if (bp->clock_p == 12 && buf[0] == '0')
1169         buf[0] = ' ';
1170
1171       for (i = 0; i < strlen(buf); i++)
1172         {
1173           flapper *f = &bp->flappers[i];
1174           f->target_index = find_index (mi, f, buf[i]);
1175         }
1176       for (; i < grid_width * grid_height; i++)
1177         {
1178           flapper *f = &bp->flappers[i];
1179           f->target_index = find_index (mi, f, ' ');
1180         }
1181       return;
1182     }
1183
1184   for (y = 0; y < grid_height; y++)
1185     {
1186       Bool nl_p = False;
1187       for (x = 0; x < grid_width; x++)
1188         {
1189           int i = y * grid_width + x;
1190           flapper *f = &bp->flappers[i];
1191           unsigned long uc = ((nl_p || cls_p) ? ' ' : read_unicode (mi));
1192           if (uc == '\r' || uc == '\n')
1193             nl_p = True;
1194           else if (uc == 12)  /* ^L */
1195             cls_p = True;
1196
1197           /* Convert Unicode to the closest Latin1 equivalent. */
1198           if (uc > 127)
1199             {
1200               Bool ascii_p = (f->spool != latin1_spool);
1201               unsigned char s[5], *s2;
1202               int L = utf8_encode (uc, (char *) s, sizeof(s));
1203               s[L] = 0;
1204               s2 = (unsigned char *) utf8_to_latin1 ((char *) s, ascii_p);
1205               
1206               if (s2[0] < 128)  /* ASCII */
1207                 uc = s2[0];
1208               else              /* Latin1 -> UTF8 -> Unicode */
1209                 {
1210                   s[0] = (s2[0] > 0xBF ? 0xC3 : 0xC2);
1211                   s[1] = s2[0] & (s2[0] > 0xBF ? 0xBF : 0xFF);
1212                   s[2] = 0;
1213                   utf8_decode (s, 2, &uc);
1214                 }
1215
1216               free (s2);
1217             }
1218
1219           /* Upcase ASCII. Upcasing Unicrud would be rocket surgery. */
1220           if (uc >= 'a' && uc <= 'z') uc += ('A'-'a');
1221
1222           f->target_index = find_index (mi, f, uc);
1223
1224           f->sticky = (((random() % 20) == 0)
1225                        ? 0.05 + frand(0.1) + frand(0.1)
1226                        : 0);
1227         }
1228     }
1229
1230 # if 0
1231   for (y = 0; y < grid_height; y++)
1232     {
1233       fprintf (stderr, "# ");
1234       for (x = 0; x < grid_width; x++)
1235         {
1236           int i = y * grid_width + x;
1237           flapper *f = &bp->flappers[i];
1238           fprintf(stderr, "%s", bp->spool[f->target_index]);
1239         }
1240       fprintf (stderr, " #\n");
1241     }
1242   fprintf (stderr, "\n");
1243 # endif
1244 }
1245
1246
1247 static void
1248 draw_flappers (ModeInfo *mi, Bool text_p)
1249 {
1250   splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
1251   int x, y;
1252   int running = 0;
1253
1254   for (y = 0; y < grid_height; y++)
1255     for (x = 0; x < grid_width; x++)
1256       {
1257         int i = (grid_height - y - 1) * grid_width + x;
1258         flapper *f = &bp->flappers[i];
1259         GLfloat xx = x;
1260         GLfloat yy = y;
1261
1262         if (bp->clock_p)
1263           {
1264             if (x >= 2) xx += COLON_WIDTH;
1265             if (x >= 4) xx += COLON_WIDTH;
1266             if (x >= 6) xx += COLON_WIDTH;
1267           }
1268
1269         xx *= 2.01;
1270         yy *= 1.98;
1271
1272         glPushMatrix();
1273         glTranslatef (xx, yy, 0);
1274         mi->polygon_count += draw_flapper (mi, f, text_p);
1275
1276         if (text_p && bp->clock_p && (x == 2 || x == 4))
1277           mi->polygon_count += draw_colon (mi);
1278
1279         glPopMatrix();
1280
1281         if (text_p)
1282           {
1283             tick_flapper (mi, f);
1284             if (f->current_index != f->target_index)
1285               running++;
1286           }
1287       }
1288
1289   if (text_p && !running)
1290     {
1291       if (bp->clock_p)
1292         fill_targets (mi);
1293       else if (bp->linger)
1294         {
1295           bp->linger--;
1296           if (!bp->linger)
1297             fill_targets (mi);
1298         }
1299       else
1300         {
1301           /* Base of 1 second, plus 1 second for every 25 characters.
1302              Also multiply by speed? */
1303           bp->linger = 30;
1304           if (!bp->first_time_p)
1305             bp->linger += (grid_width * grid_height * 1.2);
1306           bp->first_time_p = False;
1307         }
1308     }
1309 }
1310
1311
1312 ENTRYPOINT void
1313 draw_splitflap (ModeInfo *mi)
1314 {
1315   splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
1316   Display *dpy = MI_DISPLAY(mi);
1317   Window window = MI_WINDOW(mi);
1318
1319   if (!bp->glx_context)
1320     return;
1321
1322   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
1323
1324   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1325
1326   glPushMatrix ();
1327   glRotatef(current_device_rotation(), 0, 0, 1);
1328
1329   glScalef (0.1, 0.1, 0.1);  /* because of gluLookAt */
1330
1331   {
1332     double x, y, z;
1333     get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
1334     glTranslatef((x - 0.5) * 8,
1335                  (y - 0.5) * 8,
1336                  (z - 0.5) * 8);
1337
1338     gltrackball_rotate (bp->trackball);
1339
1340     if (face_front_p)
1341       {
1342         double maxx = 120;
1343         double maxy = 60;
1344         double maxz = 45;
1345         get_position (bp->rot2, &x, &y, &z, !bp->button_down_p);
1346         if (bp->spinx) glRotatef (maxy/2 - x*maxy, 1, 0, 0);
1347         if (bp->spiny) glRotatef (maxx/2 - y*maxx, 0, 1, 0);
1348         if (bp->spinz) glRotatef (maxz/2 - z*maxz, 0, 0, 1);
1349       }
1350     else
1351       {
1352         get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
1353         glRotatef (x * 360, 1, 0, 0);
1354         glRotatef (y * 360, 0, 1, 0);
1355         glRotatef (z * 360, 0, 0, 1);
1356       }
1357   }
1358
1359   /* Fit the whole grid on the screen */
1360   {
1361     GLfloat r = MI_HEIGHT(mi) / (GLfloat) MI_WIDTH(mi);
1362     int cells = (grid_width > grid_height
1363                  ? grid_width * r
1364                  : grid_height);
1365     GLfloat s = 8;
1366 # ifdef HAVE_MOBILE
1367     s *= 2; /* #### What. Why is this necessary? */
1368 #endif
1369     s /= cells;
1370     glScalef (s, s, s);
1371   }
1372
1373   mi->polygon_count = 0;
1374   mi->polygon_count += draw_component (mi, SPLITFLAP_OUTER_FRAME);
1375
1376   {
1377     GLfloat xoff = (bp->clock_p == 12 ? COLON_WIDTH * 3 :
1378                     bp->clock_p == 24 ? COLON_WIDTH * 2 :
1379                     0);
1380     glTranslatef (1 - (grid_width + xoff), 1 - grid_height, 0);
1381   }
1382
1383   /* We must render all text after all polygons, or alpha blending
1384      doesn't work right. */
1385   draw_flappers (mi, False);
1386   draw_flappers (mi, True);
1387
1388   glPopMatrix ();
1389
1390   if (mi->fps_p) do_fps (mi);
1391   glFinish();
1392
1393   glXSwapBuffers(dpy, window);
1394 }
1395
1396 ENTRYPOINT void
1397 free_splitflap (ModeInfo *mi)
1398 {
1399   splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
1400   if (bp->tc)
1401     textclient_close (bp->tc);
1402   bp->tc = 0;
1403   /* #### bp->texinfo */
1404 }
1405
1406 XSCREENSAVER_MODULE ("SplitFlap", splitflap)
1407
1408 #endif /* USE_GL */