From http://www.jwz.org/xscreensaver/xscreensaver-5.35.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 refresh_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   if (!bps) {
303     bps = (splitflap_configuration *)
304       calloc (MI_NUM_SCREENS(mi), sizeof (splitflap_configuration));
305     if (!bps) {
306       fprintf(stderr, "%s: out of memory\n", progname);
307       exit(1);
308     }
309   }
310
311   bp = &bps[MI_SCREEN(mi)];
312   bp->glx_context = init_GL(mi);
313   reshape_splitflap (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
314
315   bp->first_time_p = True;
316
317   if (!mode_str || !*mode_str || !strcasecmp(mode_str, "text"))
318     {
319       bp->clock_p = 0;
320     }
321   else if (!strcasecmp (mode_str, "clock") ||
322            !strcasecmp (mode_str, "clock12"))
323     {
324       bp->clock_p = 12;
325       grid_width  = 8;
326       grid_height = 1;
327     }
328   else if (!strcasecmp (mode_str, "clock24"))
329     {
330       bp->clock_p = 24;
331       grid_width  = 6;
332       grid_height = 1;
333     }
334   else
335     {
336       fprintf (stderr,
337            "%s: `mode' must be text, clock12 or clock24: not `%s'\n",
338                progname, mode_str);
339       exit (1);
340     }
341
342   if (! bp->clock_p)
343     {
344       bp->tc = textclient_open (MI_DISPLAY (mi));
345       bp->text[0] = 0;
346
347       if (grid_width > 10)
348         textclient_reshape (bp->tc, 
349                             grid_width, grid_height,
350                             grid_width, grid_height,
351                             0);
352     }
353
354   if (bp->clock_p)
355     speed /= 4;
356
357   glShadeModel(GL_SMOOTH);
358
359   glEnable(GL_DEPTH_TEST);
360   glEnable(GL_NORMALIZE);
361   glEnable(GL_CULL_FACE);
362
363   if (!wire)
364     {
365       GLfloat pos[4] = {0.4, 0.2, 0.4, 0.0};
366 /*      GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};*/
367       GLfloat amb[4] = {0.2, 0.2, 0.2, 1.0};
368       GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
369       GLfloat spc[4] = {1.0, 1.0, 1.0, 1.0};
370
371       glEnable(GL_LIGHTING);
372       glEnable(GL_LIGHT0);
373       glEnable(GL_DEPTH_TEST);
374       glEnable(GL_CULL_FACE);
375
376       glLightfv(GL_LIGHT0, GL_POSITION, pos);
377       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
378       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
379       glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
380
381       glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
382     }
383
384
385   {
386     double spin_speed   = 0.5;
387     double wander_speed = 0.005;
388     double tilt_speed   = 0.001;
389     double spin_accel   = 0.5;
390
391     char *s = do_spin;
392     while (*s)
393       {
394         if      (*s == 'x' || *s == 'X') bp->spinx = True;
395         else if (*s == 'y' || *s == 'Y') bp->spiny = True;
396         else if (*s == 'z' || *s == 'Z') bp->spinz = True;
397         else if (*s == '0') ;
398         else
399           {
400             fprintf (stderr,
401          "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
402                      progname, do_spin);
403             exit (1);
404           }
405         s++;
406       }
407
408     bp->rot = make_rotator (bp->spinx ? spin_speed : 0,
409                             bp->spiny ? spin_speed : 0,
410                             bp->spinz ? spin_speed : 0,
411                             spin_accel,
412                             do_wander ? wander_speed : 0,
413                             False);
414     bp->rot2 = (face_front_p
415                 ? make_rotator (0, 0, 0, 0, tilt_speed, True)
416                 : 0);
417     bp->trackball = gltrackball_init (False);
418   }
419
420   bp->dlists = (GLuint *) calloc (countof(all_objs)+1, sizeof(GLuint));
421   for (i = 0; i < countof(all_objs); i++)
422     bp->dlists[i] = glGenLists (1);
423
424   parse_color (mi, "textColor", bp->text_color);
425   for (i = 0; i < countof(all_objs); i++)
426     {
427       const struct gllist *gll = *all_objs[i];
428       char *key = 0;
429       GLfloat spec[4] = {0.4, 0.4, 0.4, 1.0};
430       GLfloat shiny = 80; /* 0-128 */
431
432       glNewList (bp->dlists[i], GL_COMPILE);
433
434       glMatrixMode(GL_MODELVIEW);
435       glPushMatrix();
436       glMatrixMode(GL_TEXTURE);
437       glPushMatrix();
438       glMatrixMode(GL_MODELVIEW);
439
440       glRotatef (-90, 1, 0, 0);
441
442       glBindTexture (GL_TEXTURE_2D, 0);
443
444       switch (i) {
445       case SPLITFLAP_QUARTER_FRAME:
446         key = "frameColor";
447         break;
448       case SPLITFLAP_OUTER_FRAME:
449         key = "caseColor";
450         break;
451       case SPLITFLAP_DISC_QUARTER:
452         key = (wire ? "frameColor" : "discColor");
453         break;
454       case SPLITFLAP_FIN_EDGE_HALF:
455       case SPLITFLAP_FIN_FACE_HALF:
456         key = "finColor";
457         break;
458       default:
459         abort();
460       }
461
462       parse_color (mi, key, bp->component_colors[i]);
463
464       if (wire && i == SPLITFLAP_FIN_EDGE_HALF)
465         bp->component_colors[i][0] = 
466         bp->component_colors[i][1] = 
467         bp->component_colors[i][2] = 0.7;
468
469       glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR,  spec);
470       glMaterialf  (GL_FRONT_AND_BACK, GL_SHININESS, shiny);
471
472       switch (i) {
473       case SPLITFLAP_OUTER_FRAME:
474         if (! splitflap_obj_outer_frame)
475           splitflap_obj_outer_frame =
476             (struct gllist *) calloc (1, sizeof(*splitflap_obj_outer_frame));
477         splitflap_obj_outer_frame->points = draw_outer_frame(mi);
478         break;
479       default:
480         renderList (gll, wire);
481         break;
482       }
483
484       glMatrixMode(GL_TEXTURE);
485       glPopMatrix();
486       glMatrixMode(GL_MODELVIEW);
487       glPopMatrix();
488
489       glEndList ();
490     }
491
492   if (grid_width < 1)  grid_width  = 1;
493   if (grid_height < 1) grid_height = 1;
494   bp->flappers = (flapper *) calloc (grid_width * grid_height,
495                                      sizeof (flapper));
496
497   for (i = 0; i < grid_width * grid_height; i++)
498     {
499       flapper *f = &bp->flappers[i];
500
501       if (!bp->clock_p)
502         {
503           f->spool = ascii_spool;
504           f->spool_size = countof (ascii_spool);
505         }
506       else
507         {
508           switch (i) {
509           case 0:
510             if (bp->clock_p == 12)
511               {
512                 f->spool = digit_s1_spool;
513                 f->spool_size = countof (digit_s1_spool);
514               }
515             else
516               {
517                 f->spool = digit_01_spool;
518                 f->spool_size = countof (digit_01_spool);
519               }
520             break;
521           case 1: case 3: case 5:
522             f->spool = digit_spool;
523             f->spool_size = countof (digit_spool);
524             break;
525           case 2: case 4:
526             f->spool = digit_05_spool;
527             f->spool_size = countof (digit_05_spool);
528             break;
529           case 6:
530             f->spool = ap_spool;
531             f->spool_size = countof (ap_spool);
532             break;
533           case 7:
534             f->spool = m_spool;
535             f->spool_size = countof (m_spool);
536             break;
537           default:
538             abort();
539           }
540         }
541
542       f->target_index = random() % f->spool_size;
543       /* f->target_index = 0; */
544       f->current_index = f->target_index;
545       f->missing = (((random() % 10) == 0)
546                     ? (random() % f->spool_size)
547                     : -1);
548     }
549
550   bp->font_data = load_texture_font (mi->dpy, "flapFont");
551   init_textures (mi);
552
553   reshape_splitflap (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
554 }
555
556
557 static int
558 draw_component (ModeInfo *mi, int i)
559 {
560   splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
561   glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE,
562                 bp->component_colors[i]);
563   glCallList (bp->dlists[i]);
564   return (*all_objs[i])->points / 3;
565 }
566
567
568 static int
569 draw_frame_quarter (ModeInfo *mi, flapper *f)
570 {
571   int count = 0;
572   glPushMatrix();
573   count += draw_component (mi, SPLITFLAP_QUARTER_FRAME);
574   glPopMatrix();
575   return count;
576 }
577
578 static int
579 draw_disc_quarter (ModeInfo *mi, flapper *f)
580 {
581   int count = 0;
582   glPushMatrix();
583   count += draw_component (mi, SPLITFLAP_DISC_QUARTER);
584   glPopMatrix();
585   return count;
586 }
587
588 static int
589 draw_fin_edge_half (ModeInfo *mi, flapper *f)
590 {
591   int count = 0;
592   glPushMatrix();
593   count += draw_component (mi, SPLITFLAP_FIN_EDGE_HALF);
594   glPopMatrix();
595   return count;
596 }
597
598 static int
599 draw_fin_face_half (ModeInfo *mi, flapper *f)
600 {
601   int count = 0;
602   if (MI_IS_WIREFRAME(mi)) return 0;
603   glPushMatrix();
604   count += draw_component (mi, SPLITFLAP_FIN_FACE_HALF);
605   glPopMatrix();
606   return count;
607 }
608
609
610 static int
611 draw_frame (ModeInfo *mi, flapper *f)
612 {
613   int count = 0;
614
615   glPushMatrix();
616
617   glFrontFace (GL_CCW);
618   count += draw_frame_quarter (mi, f);
619   count += draw_disc_quarter (mi, f);
620
621   glScalef (-1, 1, 1);
622   glFrontFace (GL_CW);
623   count += draw_frame_quarter (mi, f);
624   count += draw_disc_quarter (mi, f);
625
626   glScalef ( 1, -1, 1);
627   glFrontFace (GL_CCW);
628   count += draw_frame_quarter (mi, f);
629   count += draw_disc_quarter (mi, f);
630
631   glScalef (-1, 1, 1);
632   glFrontFace (GL_CW);
633   count += draw_frame_quarter (mi, f);
634   count += draw_disc_quarter (mi, f);
635
636   glPopMatrix();
637   return count;
638 }
639
640
641 static void
642 draw_fin_text_quad (ModeInfo *mi, flapper *f, int index, Bool top_p)
643 {
644   int wire = MI_IS_WIREFRAME(mi);
645   splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
646
647   /* 15  / is weird
648      27  ; descends too far
649      32  @ is too wide
650      59  [ descends too far
651      79  A^ is taller than the font
652      89  I` is weird
653    */
654
655   GLfloat z = 0.035;    /* Lifted off the surface by this distance */
656   GLfloat bot = 0.013;  /* Distance away from the mid gutter */
657   GLfloat scale = 1.8;  /* Scale to fill the panel */
658
659   int lh = bp->ascent + bp->descent;
660   texinfo *ti;
661   GLfloat qx0, qy0, qx1, qy1;
662   GLfloat tx0, ty0, tx1, ty1;
663   XCharStruct overall;
664   int tex_width, tex_height;
665   int i;
666
667   for (i = 0; i < bp->texinfo_size; i++)
668     {
669       ti = &bp->texinfo[i];
670       if (!strcmp (f->spool[index], ti->text))
671         break;
672     }
673   if (i >= bp->texinfo_size) abort();
674
675   overall = ti->metrics;
676   tex_width  = ti->tex_width;
677   tex_height = ti->tex_height;
678
679   if (bp->ascent < overall.ascent)
680     /* WTF! &Aacute; has a higher ascent than the font itself!
681        Scale it down so that it doesn't overlap the fin. */
682     scale *= bp->ascent / (GLfloat) overall.ascent * 0.98;
683
684   glPushMatrix();
685
686   glNormal3f (0, 0, 1);
687   glFrontFace (top_p ? GL_CCW : GL_CW);
688
689   if (! wire)
690     {
691       glBindTexture (GL_TEXTURE_2D, ti->texid);
692       enable_texture_string_parameters();
693     }
694
695   glTranslatef (0, 0, z);               /* Move to just above the surface */
696   glScalef (1.0 / lh, 1.0 / lh, 1);     /* Scale to font pixel coordinates */
697   glScalef (scale, scale, 1);           /* Fill the panel with the font */
698
699   if (!top_p)
700     {
701       glRotatef (180, 0, 0, 1);
702     }
703
704   /* Position the XCharStruct origin at 0,0 in the scene. */
705   qx0 = -overall.lbearing;
706   qy0 = -overall.descent;
707   qx1 =  overall.rbearing;
708   qy1 =  overall.ascent;
709
710   /* Center horizontally. */
711   qx0 -= (overall.rbearing - overall.lbearing) / 2.0;
712   qx1 -= (overall.rbearing - overall.lbearing) / 2.0;
713
714
715   /* Move origin to below font descenders. */
716   qy0 += bp->descent;
717   qy1 += bp->descent;
718
719   /* Center vertically. */
720   qy0 -= (bp->ascent + bp->descent) / 2.0;
721   qy1 -= (bp->ascent + bp->descent) / 2.0;
722
723   /* Move the descenders down a bit, if there's room.
724      This means that weirdos like [ and $ might not be on the baseline.
725      #### This looks good with X11 fonts but bad with MacOS fonts.  WTF?
726    */
727 #ifndef HAVE_COCOA
728   {
729     GLfloat off = bp->descent / 3.0;
730     GLfloat max = bp->descent - off;
731     if (overall.descent > max)
732       off = max - overall.descent;
733     if (off < 0)
734       off = 0;
735     qy0 -= off;
736     qy1 -= off;
737   }
738 # endif /* !HAVE_COCOA */
739
740   /* Attach the texture to the quad. */
741   tx0 = 0;
742   ty1 = 0;
743   tx1 = (overall.rbearing - overall.lbearing) / (GLfloat) tex_width;
744   ty0 = (overall.ascent   + overall.descent)  / (GLfloat) tex_height;
745
746   /* Convert from font ascent/descent to character ascent/descent. */
747
748   /* Flip texture horizontally on bottom panel. */
749   if (!top_p)
750     {
751       GLfloat s = tx0;
752       tx0 = tx1;
753       tx1 = s;
754     }
755
756
757   /* Cut the character in half, truncating just above the split line. */
758   {
759     GLfloat oqy0 = qy0;
760     GLfloat oqy1 = qy1;
761     GLfloat r0, r1;
762
763     bot *= lh * scale;
764
765     if (top_p)
766       {
767         if (qy0 < bot)
768           qy0 = bot;
769       }
770     else
771       {
772         if (qy1 > -bot)
773           qy1 = -bot;
774       }
775
776     r0 = (qy0 - oqy0) / (oqy1 - oqy0);
777     r1 = (qy1 - oqy1) / (oqy1 - oqy0);
778     ty0 -= r0 * (ty0 - ty1);
779     ty1 -= r1 * (ty0 - ty1);
780   }
781
782   glColor4fv (bp->text_color);
783   glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
784   glTexCoord2f (tx0, ty0); glVertex3f (qx0, qy0, 0);
785   glTexCoord2f (tx1, ty0); glVertex3f (qx1, qy0, 0);
786   glTexCoord2f (tx1, ty1); glVertex3f (qx1, qy1, 0);
787   glTexCoord2f (tx0, ty1); glVertex3f (qx0, qy1, 0);
788   glEnd();
789
790   glPopMatrix();
791
792   if (! wire)
793     {
794       glDisable (GL_BLEND);
795       glEnable (GL_LIGHTING);
796       glDisable (GL_TEXTURE_2D);
797     }
798 }
799
800
801 static int
802 draw_fin (ModeInfo *mi, flapper *f, int front_index, int back_index,
803           Bool text_p)
804 {
805   int count = 0;
806
807   glPushMatrix();
808
809   glFrontFace (GL_CCW);
810
811   if (! text_p)
812     count += draw_fin_edge_half (mi, f);
813
814   if (front_index >= 0)
815     {
816       if (text_p)
817         {
818           draw_fin_text_quad (mi, f, front_index, True);
819           count++;
820         }
821       else
822         count += draw_fin_face_half (mi, f);
823     }
824
825   glScalef (-1, 1, 1);
826   if (! text_p)
827     {
828       glFrontFace (GL_CW);
829       count += draw_fin_edge_half (mi, f);
830       if (front_index >= 0)
831         count += draw_fin_face_half (mi, f);
832     }
833
834   if (back_index >= 0)
835     {
836       glRotatef (180, 0, 1, 0);
837       if (text_p)
838         {
839           draw_fin_text_quad (mi, f, back_index, False);
840           count++;
841         }
842       else
843         {
844           count += draw_fin_face_half (mi, f);
845           glScalef (-1, 1, 1);
846           glFrontFace (GL_CCW);
847           count += draw_fin_face_half (mi, f);
848         }
849     }
850
851   glPopMatrix();
852   return count;
853 }
854
855
856 /* The case holding the grid of flappers.
857  */
858 static int
859 draw_outer_frame (ModeInfo *mi)
860 {
861   splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
862   int count = 0;
863   GLfloat w = grid_width;
864   GLfloat h = grid_height;
865   GLfloat d = 1;
866
867   if (bp->clock_p == 12)
868     w += COLON_WIDTH * 3;
869   else if (bp->clock_p == 24)
870     w += COLON_WIDTH * 2;
871
872   w += 0.2;
873   h += 0.2;
874
875   if (bp->clock_p) w += 0.25;
876   if (w > 3) w += 0.5;
877   if (h > 3) h += 0.5;
878
879   if (MI_IS_WIREFRAME(mi))
880     return 0;
881
882   glFrontFace (GL_CCW);
883   glPushMatrix();
884   glTranslatef (0, 1.03, 0);
885
886   glBegin (GL_QUADS);
887
888   glNormal3f ( 0,  1,  0);      /* back */
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, -1,  0);      /* front */
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);      /* top */
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 ( 0,  0, -1);      /* bottom */
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);      /* left */
917   glVertex3f ( w, -d,  h);
918   glVertex3f ( w, -d, -h);
919   glVertex3f ( w,  d, -h);
920   glVertex3f ( w,  d,  h);
921   count++;
922
923   glNormal3f (-1,  0,  0);      /* right */
924   glVertex3f (-w, -d, -h);
925   glVertex3f (-w, -d,  h);
926   glVertex3f (-w,  d,  h);
927   glVertex3f (-w,  d, -h);
928   count++;
929
930   glEnd();
931   glPopMatrix();
932
933   return count;
934 }
935
936
937 static void
938 tick_flapper (ModeInfo *mi, flapper *f)
939 {
940   splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
941   double prev = f->current_index;
942   Bool wrapped_p = False;
943
944   if (bp->button_down_p) return;
945   if (f->current_index == f->target_index)
946     return;
947
948   f->current_index += speed * 0.35;             /* turn the crank */
949
950   while (f->current_index > f->spool_size)
951     {
952       f->current_index -= f->spool_size;
953       wrapped_p = True;
954     }
955
956   if (f->current_index < 0) abort();
957
958   if ((prev < f->target_index || wrapped_p) &&
959       f->current_index > f->target_index)       /* just overshot */
960     f->current_index = f->target_index;
961 }
962
963
964 #define MOD(M,N) (((M)+(N)) % (N))  /* Works with negatives */
965
966 static int
967 draw_flapper (ModeInfo *mi, flapper *f, Bool text_p)
968 {
969   int prev_index = floor (f->current_index);
970   int next_index = MOD (prev_index+1, f->spool_size);
971   int count = 0;
972   GLfloat epsilon = 0.02;
973   GLfloat r = f->current_index - prev_index;
974   Bool moving_p = (r > 0 && r < 1);
975   GLfloat sticky = f->sticky;
976
977   if (f->missing >= 0)
978     sticky = 0;
979
980   if (f->missing >= 0 &&
981       MOD (prev_index, f->spool_size) == f->missing)
982     {
983       moving_p = False;
984       sticky = 0;
985     }
986
987   if (!moving_p)
988     next_index = prev_index;
989
990   if (! text_p)
991     count += draw_frame (mi, f);
992
993   /* Top flap, flat: top half of target char */
994   if (!moving_p || !text_p || r > epsilon)
995     {
996       int p2 = next_index;
997
998       if (p2 == f->missing)
999         p2 = MOD (p2+1, f->spool_size);
1000
1001       count += draw_fin (mi, f, p2, -1, text_p);
1002     }
1003
1004   /* Bottom flap, flat: bottom half of prev char */
1005   if (!moving_p || !text_p || r < 1 - epsilon)
1006     {
1007       int p2 = prev_index;
1008
1009       if (!moving_p && sticky)
1010         p2 = MOD (p2-1, f->spool_size);
1011
1012       if (f->missing >= 0 &&
1013           p2 == MOD (f->missing+1, f->spool_size))
1014         p2 = MOD (p2-1, f->spool_size);
1015
1016       glPushMatrix();
1017       glRotatef (180, 1, 0, 0);
1018       count += draw_fin (mi, f, -1, p2, text_p);
1019       glPopMatrix();
1020     }
1021
1022   /* Moving flap, front: top half of prev char */
1023   /* Moving flap, back: bottom half of target char */
1024   if (moving_p || sticky)
1025     {
1026       if (!moving_p)
1027         r = 1.0;
1028       if (sticky && r > 1 - sticky)
1029         r = 1 - sticky;
1030       glPushMatrix();
1031       glRotatef (r * 180, 1, 0, 0);
1032       count += draw_fin (mi, f, prev_index, next_index, text_p);
1033       glPopMatrix();
1034     }
1035
1036   return count;
1037 }
1038
1039
1040 static int
1041 draw_colon (ModeInfo *mi)
1042 {
1043   splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
1044   GLfloat s = 1.0 / (bp->ascent + bp->descent);
1045   GLfloat z = 0.01;
1046   int count = 0;
1047   XCharStruct m;
1048
1049   texture_string_metrics (bp->font_data, ":", &m, 0, 0);
1050
1051   s *= 2;
1052
1053   glPushMatrix();
1054
1055   glTranslatef (-(1 + COLON_WIDTH), 0, 0);
1056   glScalef (s, s, 1);
1057
1058   glTranslatef (-m.lbearing - (m.rbearing - m.lbearing)/2,
1059                 -(m.ascent + m.descent) / 2,
1060                 0);
1061
1062   glEnable (GL_TEXTURE_2D);
1063
1064   /* draw the text five times, to give it a border. */
1065   {
1066     const XPoint offsets[] = {{ -1, -1 },
1067                               { -1,  1 },
1068                               {  1,  1 },
1069                               {  1, -1 },
1070                               {  0,  0 }};
1071     int i;
1072     GLfloat n = 1.5;
1073
1074     glColor3f (0, 0, 0);
1075     for (i = 0; i < countof(offsets); i++)
1076       {
1077         glPushMatrix();
1078         if (offsets[i].x == 0)
1079           {
1080             glColor4fv (bp->text_color);
1081             glTranslatef (0, 0, z * 2);
1082           }
1083         glTranslatef (n * offsets[i].x, n * offsets[i].y, 0);
1084         print_texture_string (bp->font_data, ":");
1085         count++;
1086         glPopMatrix();
1087       }
1088   }
1089
1090   glPopMatrix();
1091
1092   return count;
1093 }
1094
1095
1096 /* Reads and returns a single Unicode character from the text client.
1097  */
1098 static unsigned long
1099 read_unicode (ModeInfo *mi)
1100 {
1101   splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
1102   const unsigned char *end = bp->text + sizeof(bp->text) - 1;  /* 4 bytes */
1103   unsigned long uc = 0;
1104   long L;
1105   int i;
1106
1107   if (bp->clock_p || !bp->tc) abort();
1108
1109   /* Fill the buffer with available input.
1110    */
1111   i = strlen ((char *) bp->text);
1112   while (i < (end - bp->text))
1113     {
1114       int c = textclient_getc (bp->tc);
1115       if (c <= 0) break;
1116       bp->text[i++] = (char) c;
1117       bp->text[i] = 0;
1118     }
1119
1120   /* Pop 1-4 bytes from the front of the buffer and extract a UTF8 character.
1121    */
1122   L = utf8_decode (bp->text, i, &uc);
1123   if (L)
1124     {
1125       int j = end - bp->text - L;
1126       memmove (bp->text, bp->text + L, j);
1127       bp->text[j] = 0;
1128     }
1129   else
1130     uc = 0;
1131
1132   return uc;
1133 }
1134
1135
1136 /* Given a Unicode character, finds the corresponding index on the spool,
1137    if any. Returns 0 if not found.
1138  */
1139 static int
1140 find_index (ModeInfo *mi, flapper *f, long uc)
1141 {
1142   char string[5];
1143   int L = utf8_encode (uc, string, sizeof(string) - 1);
1144   int i;
1145   if (L <= 0) return 0;
1146   string[L] = 0;
1147   for (i = 0; i < f->spool_size; i++)
1148     {
1149       if (!strcmp (string, f->spool[i]))
1150         return i;
1151     }
1152   return 0;
1153 }
1154
1155
1156 /* Read input from the text client and populate the spool with it.
1157  */
1158 static void
1159 fill_targets (ModeInfo *mi)
1160 {
1161   splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
1162   int x, y;
1163   Bool cls_p = False;
1164
1165   if (bp->clock_p)
1166     {
1167       char buf[80];
1168       time_t now = time ((time_t *) 0);
1169         struct tm *tm = localtime (&now);
1170       const char *fmt = (bp->clock_p == 24
1171                          ? "%H%M%S"
1172                          : "%I%M%S%p");
1173       int i;
1174       strftime (buf, sizeof(buf)-1, fmt, tm);
1175       if (bp->clock_p == 12 && buf[0] == '0')
1176         buf[0] = ' ';
1177
1178       for (i = 0; i < strlen(buf); i++)
1179         {
1180           flapper *f = &bp->flappers[i];
1181           f->target_index = find_index (mi, f, buf[i]);
1182         }
1183       for (; i < grid_width * grid_height; i++)
1184         {
1185           flapper *f = &bp->flappers[i];
1186           f->target_index = find_index (mi, f, ' ');
1187         }
1188       return;
1189     }
1190
1191   for (y = 0; y < grid_height; y++)
1192     {
1193       Bool nl_p = False;
1194       for (x = 0; x < grid_width; x++)
1195         {
1196           int i = y * grid_width + x;
1197           flapper *f = &bp->flappers[i];
1198           unsigned long uc = ((nl_p || cls_p) ? ' ' : read_unicode (mi));
1199           if (uc == '\r' || uc == '\n')
1200             nl_p = True;
1201           else if (uc == 12)  /* ^L */
1202             cls_p = True;
1203
1204           /* Convert Unicode to the closest Latin1 equivalent. */
1205           if (uc > 127)
1206             {
1207               Bool ascii_p = (f->spool != latin1_spool);
1208               unsigned char s[5], *s2;
1209               int L = utf8_encode (uc, (char *) s, sizeof(s));
1210               s[L] = 0;
1211               s2 = (unsigned char *) utf8_to_latin1 ((char *) s, ascii_p);
1212               
1213               if (s2[0] < 128)  /* ASCII */
1214                 uc = s2[0];
1215               else              /* Latin1 -> UTF8 -> Unicode */
1216                 {
1217                   s[0] = (s2[0] > 0xBF ? 0xC3 : 0xC2);
1218                   s[1] = s2[0] & (s2[0] > 0xBF ? 0xBF : 0xFF);
1219                   s[2] = 0;
1220                   utf8_decode (s, 2, &uc);
1221                 }
1222
1223               free (s2);
1224             }
1225
1226           /* Upcase ASCII. Upcasing Unicrud would be rocket surgery. */
1227           if (uc >= 'a' && uc <= 'z') uc += ('A'-'a');
1228
1229           f->target_index = find_index (mi, f, uc);
1230
1231           f->sticky = (((random() % 20) == 0)
1232                        ? 0.05 + frand(0.1) + frand(0.1)
1233                        : 0);
1234         }
1235     }
1236
1237 # if 0
1238   for (y = 0; y < grid_height; y++)
1239     {
1240       fprintf (stderr, "# ");
1241       for (x = 0; x < grid_width; x++)
1242         {
1243           int i = y * grid_width + x;
1244           flapper *f = &bp->flappers[i];
1245           fprintf(stderr, "%s", bp->spool[f->target_index]);
1246         }
1247       fprintf (stderr, " #\n");
1248     }
1249   fprintf (stderr, "\n");
1250 # endif
1251 }
1252
1253
1254 static void
1255 draw_flappers (ModeInfo *mi, Bool text_p)
1256 {
1257   splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
1258   int x, y;
1259   int running = 0;
1260
1261   for (y = 0; y < grid_height; y++)
1262     for (x = 0; x < grid_width; x++)
1263       {
1264         int i = (grid_height - y - 1) * grid_width + x;
1265         flapper *f = &bp->flappers[i];
1266         GLfloat xx = x;
1267         GLfloat yy = y;
1268
1269         if (bp->clock_p)
1270           {
1271             if (x >= 2) xx += COLON_WIDTH;
1272             if (x >= 4) xx += COLON_WIDTH;
1273             if (x >= 6) xx += COLON_WIDTH;
1274           }
1275
1276         xx *= 2.01;
1277         yy *= 1.98;
1278
1279         glPushMatrix();
1280         glTranslatef (xx, yy, 0);
1281         mi->polygon_count += draw_flapper (mi, f, text_p);
1282
1283         if (text_p && bp->clock_p && (x == 2 || x == 4))
1284           mi->polygon_count += draw_colon (mi);
1285
1286         glPopMatrix();
1287
1288         if (text_p)
1289           {
1290             tick_flapper (mi, f);
1291             if (f->current_index != f->target_index)
1292               running++;
1293           }
1294       }
1295
1296   if (text_p && !running)
1297     {
1298       if (bp->clock_p)
1299         fill_targets (mi);
1300       else if (bp->linger)
1301         {
1302           bp->linger--;
1303           if (!bp->linger)
1304             fill_targets (mi);
1305         }
1306       else
1307         {
1308           /* Base of 1 second, plus 1 second for every 25 characters.
1309              Also multiply by speed? */
1310           bp->linger = 30;
1311           if (!bp->first_time_p)
1312             bp->linger += (grid_width * grid_height * 1.2);
1313           bp->first_time_p = False;
1314         }
1315     }
1316 }
1317
1318
1319 ENTRYPOINT void
1320 draw_splitflap (ModeInfo *mi)
1321 {
1322   splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
1323   Display *dpy = MI_DISPLAY(mi);
1324   Window window = MI_WINDOW(mi);
1325
1326   if (!bp->glx_context)
1327     return;
1328
1329   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
1330
1331   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1332
1333   glPushMatrix ();
1334   glRotatef(current_device_rotation(), 0, 0, 1);
1335
1336   glScalef (0.1, 0.1, 0.1);  /* because of gluLookAt */
1337
1338   {
1339     double x, y, z;
1340     get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
1341     glTranslatef((x - 0.5) * 8,
1342                  (y - 0.5) * 8,
1343                  (z - 0.5) * 8);
1344
1345     gltrackball_rotate (bp->trackball);
1346
1347     if (face_front_p)
1348       {
1349         double maxx = 120;
1350         double maxy = 60;
1351         double maxz = 45;
1352         get_position (bp->rot2, &x, &y, &z, !bp->button_down_p);
1353         if (bp->spinx) glRotatef (maxy/2 - x*maxy, 1, 0, 0);
1354         if (bp->spiny) glRotatef (maxx/2 - y*maxx, 0, 1, 0);
1355         if (bp->spinz) glRotatef (maxz/2 - z*maxz, 0, 0, 1);
1356       }
1357     else
1358       {
1359         get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
1360         glRotatef (x * 360, 1, 0, 0);
1361         glRotatef (y * 360, 0, 1, 0);
1362         glRotatef (z * 360, 0, 0, 1);
1363       }
1364   }
1365
1366   /* Fit the whole grid on the screen */
1367   {
1368     GLfloat r = MI_HEIGHT(mi) / (GLfloat) MI_WIDTH(mi);
1369     int cells = (grid_width > grid_height
1370                  ? grid_width * r
1371                  : grid_height);
1372     GLfloat s = 8;
1373 # ifdef HAVE_MOBILE
1374     s *= 2; /* #### What. Why is this necessary? */
1375 #endif
1376     s /= cells;
1377     glScalef (s, s, s);
1378   }
1379
1380   mi->polygon_count = 0;
1381   mi->polygon_count += draw_component (mi, SPLITFLAP_OUTER_FRAME);
1382
1383   {
1384     GLfloat xoff = (bp->clock_p == 12 ? COLON_WIDTH * 3 :
1385                     bp->clock_p == 24 ? COLON_WIDTH * 2 :
1386                     0);
1387     glTranslatef (1 - (grid_width + xoff), 1 - grid_height, 0);
1388   }
1389
1390   /* We must render all text after all polygons, or alpha blending
1391      doesn't work right. */
1392   draw_flappers (mi, False);
1393   draw_flappers (mi, True);
1394
1395   glPopMatrix ();
1396
1397   if (mi->fps_p) do_fps (mi);
1398   glFinish();
1399
1400   glXSwapBuffers(dpy, window);
1401 }
1402
1403 ENTRYPOINT void
1404 release_splitflap (ModeInfo *mi)
1405 {
1406   splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
1407   if (bp->tc)
1408     textclient_close (bp->tc);
1409   /* #### bp->texinfo */
1410 }
1411
1412 XSCREENSAVER_MODULE ("SplitFlap", splitflap)
1413
1414 #endif /* USE_GL */