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