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