1 /* splitflap, Copyright (c) 2015 Jamie Zawinski <jwz@jwz.org>
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
11 * Draws a split-flap text display.
14 #define FLAP_FONT "-*-helvetica-bold-r-normal-*-*-1440-*-*-*-*-*-*"
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" \
29 # define refresh_splitflap 0
31 #define countof(x) (sizeof((x))/sizeof((*x)))
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"
41 #include "xlockmore.h"
45 #ifdef USE_GL /* whole file */
47 #include "gltrackball.h"
49 #include "xpm-ximage.h"
51 #include "textclient.h"
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;
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
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
72 #define COLON_WIDTH 0.5
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 */
87 int tex_width, tex_height;
91 GLXContext *glx_context;
93 trackball_state *trackball;
95 Bool spinx, spiny, spinz;
101 GLfloat component_colors[countof(all_objs)][4];
102 GLfloat text_color[4];
104 flapper *flappers; /* grid_width * grid_height */
106 texture_font_data *font_data;
110 unsigned char text[5];
115 } splitflap_configuration;
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"
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 "[", "\\", "]", "^", "_", "`", "{", "|", "}", "~",
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 "[", "\\", "]", "^", "_", "`", "{", "|", "}", "~",
147 "\302\241", "\302\242", "\302\243", "\302\245",
148 "\302\247", "\302\251", "\302\265", "\302\266",
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",
161 static splitflap_configuration *bps = NULL;
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;
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 },
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},
193 ENTRYPOINT ModeSpecOpt splitflap_opts = {
194 countof(opts), opts, countof(vars), vars, NULL};
197 /* Window management, etc
200 reshape_splitflap (ModeInfo *mi, int width, int height)
202 GLfloat h = (GLfloat) height / (GLfloat) width;
204 glViewport (0, 0, (GLint) width, (GLint) height);
206 glMatrixMode(GL_PROJECTION);
208 gluPerspective (40.0, 1/h, 0.5, 25);
210 glMatrixMode(GL_MODELVIEW);
212 gluLookAt( 0, 0, 3, /* 10x lower than traditional, for better depth rez */
216 # ifdef HAVE_MOBILE /* Keep it the same relative size when rotated. */
218 int o = (int) current_device_rotation();
219 if (o != 0 && o != 180 && o != -180)
224 glClear(GL_COLOR_BUFFER_BIT);
229 splitflap_handle_event (ModeInfo *mi, XEvent *event)
231 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
233 if (gltrackball_event_handler (event, bp->trackball,
234 MI_WIDTH (mi), MI_HEIGHT (mi),
243 init_textures (ModeInfo *mi)
245 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
247 const char * const *spool = latin1_spool;
248 int max = countof(latin1_spool);
250 bp->texinfo = (texinfo *) calloc (max+1, sizeof(*bp->texinfo));
251 texture_string_metrics (bp->font_data, "", 0, &bp->ascent, &bp->descent);
253 for (i = 0; i < max; i++)
255 texinfo *ti = &bp->texinfo[i];
256 glGenTextures (1, &ti->texid);
257 glBindTexture (GL_TEXTURE_2D, ti->texid);
261 /* fprintf(stderr, "%d \\%03o\\%03o %s\n", i,
262 (unsigned char) ti->text[0],
263 (unsigned char) ti->text[1],
266 string_to_texture (bp->font_data, ti->text, &ti->metrics,
267 &ti->tex_width, &ti->tex_height);
269 bp->texinfo_size = i;
271 glBindTexture (GL_TEXTURE_2D, 0);
276 parse_color (ModeInfo *mi, char *key, GLfloat color[4])
279 char *string = get_string_resource (mi->dpy, key, "Color");
280 if (!XParseColor (mi->dpy, mi->xgwa.colormap, string, &xcolor))
282 fprintf (stderr, "%s: unparsable color in %s: %s\n", progname,
287 color[0] = xcolor.red / 65536.0;
288 color[1] = xcolor.green / 65536.0;
289 color[2] = xcolor.blue / 65536.0;
294 static int draw_outer_frame (ModeInfo *mi);
297 init_splitflap (ModeInfo *mi)
299 splitflap_configuration *bp;
300 int wire = MI_IS_WIREFRAME(mi);
303 bps = (splitflap_configuration *)
304 calloc (MI_NUM_SCREENS(mi), sizeof (splitflap_configuration));
306 fprintf(stderr, "%s: out of memory\n", progname);
311 bp = &bps[MI_SCREEN(mi)];
312 bp->glx_context = init_GL(mi);
313 reshape_splitflap (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
315 bp->first_time_p = True;
317 if (!mode_str || !*mode_str || !strcasecmp(mode_str, "text"))
321 else if (!strcasecmp (mode_str, "clock") ||
322 !strcasecmp (mode_str, "clock12"))
328 else if (!strcasecmp (mode_str, "clock24"))
337 "%s: `mode' must be text, clock12 or clock24: not `%s'\n",
344 bp->tc = textclient_open (MI_DISPLAY (mi));
348 textclient_reshape (bp->tc,
349 grid_width, grid_height,
350 grid_width, grid_height,
357 glShadeModel(GL_SMOOTH);
359 glEnable(GL_DEPTH_TEST);
360 glEnable(GL_NORMALIZE);
361 glEnable(GL_CULL_FACE);
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};
371 glEnable(GL_LIGHTING);
373 glEnable(GL_DEPTH_TEST);
374 glEnable(GL_CULL_FACE);
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);
381 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
386 double spin_speed = 0.5;
387 double wander_speed = 0.005;
388 double tilt_speed = 0.001;
389 double spin_accel = 0.5;
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') ;
401 "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
408 bp->rot = make_rotator (bp->spinx ? spin_speed : 0,
409 bp->spiny ? spin_speed : 0,
410 bp->spinz ? spin_speed : 0,
412 do_wander ? wander_speed : 0,
414 bp->rot2 = (face_front_p
415 ? make_rotator (0, 0, 0, 0, tilt_speed, True)
417 bp->trackball = gltrackball_init (False);
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);
424 parse_color (mi, "textColor", bp->text_color);
425 for (i = 0; i < countof(all_objs); i++)
427 const struct gllist *gll = *all_objs[i];
429 GLfloat spec[4] = {0.4, 0.4, 0.4, 1.0};
430 GLfloat shiny = 80; /* 0-128 */
432 glNewList (bp->dlists[i], GL_COMPILE);
434 glMatrixMode(GL_MODELVIEW);
436 glMatrixMode(GL_TEXTURE);
438 glMatrixMode(GL_MODELVIEW);
440 glRotatef (-90, 1, 0, 0);
442 glBindTexture (GL_TEXTURE_2D, 0);
445 case SPLITFLAP_QUARTER_FRAME:
448 case SPLITFLAP_OUTER_FRAME:
451 case SPLITFLAP_DISC_QUARTER:
452 key = (wire ? "frameColor" : "discColor");
454 case SPLITFLAP_FIN_EDGE_HALF:
455 case SPLITFLAP_FIN_FACE_HALF:
462 parse_color (mi, key, bp->component_colors[i]);
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;
469 glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, spec);
470 glMaterialf (GL_FRONT_AND_BACK, GL_SHININESS, shiny);
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);
480 renderList (gll, wire);
484 glMatrixMode(GL_TEXTURE);
486 glMatrixMode(GL_MODELVIEW);
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,
497 for (i = 0; i < grid_width * grid_height; i++)
499 flapper *f = &bp->flappers[i];
503 f->spool = ascii_spool;
504 f->spool_size = countof (ascii_spool);
510 if (bp->clock_p == 12)
512 f->spool = digit_s1_spool;
513 f->spool_size = countof (digit_s1_spool);
517 f->spool = digit_01_spool;
518 f->spool_size = countof (digit_01_spool);
521 case 1: case 3: case 5:
522 f->spool = digit_spool;
523 f->spool_size = countof (digit_spool);
526 f->spool = digit_05_spool;
527 f->spool_size = countof (digit_05_spool);
531 f->spool_size = countof (ap_spool);
535 f->spool_size = countof (m_spool);
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)
550 bp->font_data = load_texture_font (mi->dpy, "flapFont");
553 reshape_splitflap (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
558 draw_component (ModeInfo *mi, int i)
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;
569 draw_frame_quarter (ModeInfo *mi, flapper *f)
573 count += draw_component (mi, SPLITFLAP_QUARTER_FRAME);
579 draw_disc_quarter (ModeInfo *mi, flapper *f)
583 count += draw_component (mi, SPLITFLAP_DISC_QUARTER);
589 draw_fin_edge_half (ModeInfo *mi, flapper *f)
593 count += draw_component (mi, SPLITFLAP_FIN_EDGE_HALF);
599 draw_fin_face_half (ModeInfo *mi, flapper *f)
602 if (MI_IS_WIREFRAME(mi)) return 0;
604 count += draw_component (mi, SPLITFLAP_FIN_FACE_HALF);
611 draw_frame (ModeInfo *mi, flapper *f)
617 glFrontFace (GL_CCW);
618 count += draw_frame_quarter (mi, f);
619 count += draw_disc_quarter (mi, f);
623 count += draw_frame_quarter (mi, f);
624 count += draw_disc_quarter (mi, f);
626 glScalef ( 1, -1, 1);
627 glFrontFace (GL_CCW);
628 count += draw_frame_quarter (mi, f);
629 count += draw_disc_quarter (mi, f);
633 count += draw_frame_quarter (mi, f);
634 count += draw_disc_quarter (mi, f);
642 draw_fin_text_quad (ModeInfo *mi, flapper *f, int index, Bool top_p)
644 int wire = MI_IS_WIREFRAME(mi);
645 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
648 27 ; descends too far
650 59 [ descends too far
651 79 A^ is taller than the font
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 */
659 int lh = bp->ascent + bp->descent;
661 GLfloat qx0, qy0, qx1, qy1;
662 GLfloat tx0, ty0, tx1, ty1;
664 int tex_width, tex_height;
667 for (i = 0; i < bp->texinfo_size; i++)
669 ti = &bp->texinfo[i];
670 if (!strcmp (f->spool[index], ti->text))
673 if (i >= bp->texinfo_size) abort();
675 overall = ti->metrics;
676 tex_width = ti->tex_width;
677 tex_height = ti->tex_height;
679 if (bp->ascent < overall.ascent)
680 /* WTF! Á 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;
686 glNormal3f (0, 0, 1);
687 glFrontFace (top_p ? GL_CCW : GL_CW);
691 glBindTexture (GL_TEXTURE_2D, ti->texid);
692 enable_texture_string_parameters();
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 */
701 glRotatef (180, 0, 0, 1);
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;
710 /* Center horizontally. */
711 qx0 -= (overall.rbearing - overall.lbearing) / 2.0;
712 qx1 -= (overall.rbearing - overall.lbearing) / 2.0;
715 /* Move origin to below font descenders. */
719 /* Center vertically. */
720 qy0 -= (bp->ascent + bp->descent) / 2.0;
721 qy1 -= (bp->ascent + bp->descent) / 2.0;
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?
729 GLfloat off = bp->descent / 3.0;
730 GLfloat max = bp->descent - off;
731 if (overall.descent > max)
732 off = max - overall.descent;
738 # endif /* !HAVE_COCOA */
740 /* Attach the texture to the quad. */
743 tx1 = (overall.rbearing - overall.lbearing) / (GLfloat) tex_width;
744 ty0 = (overall.ascent + overall.descent) / (GLfloat) tex_height;
746 /* Convert from font ascent/descent to character ascent/descent. */
748 /* Flip texture horizontally on bottom panel. */
757 /* Cut the character in half, truncating just above the split line. */
776 r0 = (qy0 - oqy0) / (oqy1 - oqy0);
777 r1 = (qy1 - oqy1) / (oqy1 - oqy0);
778 ty0 -= r0 * (ty0 - ty1);
779 ty1 -= r1 * (ty0 - ty1);
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);
794 glDisable (GL_BLEND);
795 glEnable (GL_LIGHTING);
796 glDisable (GL_TEXTURE_2D);
802 draw_fin (ModeInfo *mi, flapper *f, int front_index, int back_index,
809 glFrontFace (GL_CCW);
812 count += draw_fin_edge_half (mi, f);
814 if (front_index >= 0)
818 draw_fin_text_quad (mi, f, front_index, True);
822 count += draw_fin_face_half (mi, f);
829 count += draw_fin_edge_half (mi, f);
830 if (front_index >= 0)
831 count += draw_fin_face_half (mi, f);
836 glRotatef (180, 0, 1, 0);
839 draw_fin_text_quad (mi, f, back_index, False);
844 count += draw_fin_face_half (mi, f);
846 glFrontFace (GL_CCW);
847 count += draw_fin_face_half (mi, f);
856 /* The case holding the grid of flappers.
859 draw_outer_frame (ModeInfo *mi)
861 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
863 GLfloat w = grid_width;
864 GLfloat h = grid_height;
867 if (bp->clock_p == 12)
868 w += COLON_WIDTH * 3;
869 else if (bp->clock_p == 24)
870 w += COLON_WIDTH * 2;
875 if (bp->clock_p) w += 0.25;
879 if (MI_IS_WIREFRAME(mi))
882 glFrontFace (GL_CCW);
884 glTranslatef (0, 1.03, 0);
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);
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);
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);
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);
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);
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);
938 tick_flapper (ModeInfo *mi, flapper *f)
940 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
941 double prev = f->current_index;
942 Bool wrapped_p = False;
944 if (bp->button_down_p) return;
945 if (f->current_index == f->target_index)
948 f->current_index += speed * 0.35; /* turn the crank */
950 while (f->current_index > f->spool_size)
952 f->current_index -= f->spool_size;
956 if (f->current_index < 0) abort();
958 if ((prev < f->target_index || wrapped_p) &&
959 f->current_index > f->target_index) /* just overshot */
960 f->current_index = f->target_index;
964 #define MOD(M,N) (((M)+(N)) % (N)) /* Works with negatives */
967 draw_flapper (ModeInfo *mi, flapper *f, Bool text_p)
969 int prev_index = floor (f->current_index);
970 int next_index = MOD (prev_index+1, f->spool_size);
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;
980 if (f->missing >= 0 &&
981 MOD (prev_index, f->spool_size) == f->missing)
988 next_index = prev_index;
991 count += draw_frame (mi, f);
993 /* Top flap, flat: top half of target char */
994 if (!moving_p || !text_p || r > epsilon)
998 if (p2 == f->missing)
999 p2 = MOD (p2+1, f->spool_size);
1001 count += draw_fin (mi, f, p2, -1, text_p);
1004 /* Bottom flap, flat: bottom half of prev char */
1005 if (!moving_p || !text_p || r < 1 - epsilon)
1007 int p2 = prev_index;
1009 if (!moving_p && sticky)
1010 p2 = MOD (p2-1, f->spool_size);
1012 if (f->missing >= 0 &&
1013 p2 == MOD (f->missing+1, f->spool_size))
1014 p2 = MOD (p2-1, f->spool_size);
1017 glRotatef (180, 1, 0, 0);
1018 count += draw_fin (mi, f, -1, p2, text_p);
1022 /* Moving flap, front: top half of prev char */
1023 /* Moving flap, back: bottom half of target char */
1024 if (moving_p || sticky)
1028 if (sticky && r > 1 - sticky)
1031 glRotatef (r * 180, 1, 0, 0);
1032 count += draw_fin (mi, f, prev_index, next_index, text_p);
1041 draw_colon (ModeInfo *mi)
1043 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
1044 GLfloat s = 1.0 / (bp->ascent + bp->descent);
1049 texture_string_metrics (bp->font_data, ":", &m, 0, 0);
1055 glTranslatef (-(1 + COLON_WIDTH), 0, 0);
1058 glTranslatef (-m.lbearing - (m.rbearing - m.lbearing)/2,
1059 -(m.ascent + m.descent) / 2,
1062 glEnable (GL_TEXTURE_2D);
1064 /* draw the text five times, to give it a border. */
1066 const XPoint offsets[] = {{ -1, -1 },
1074 glColor3f (0, 0, 0);
1075 for (i = 0; i < countof(offsets); i++)
1078 if (offsets[i].x == 0)
1080 glColor4fv (bp->text_color);
1081 glTranslatef (0, 0, z * 2);
1083 glTranslatef (n * offsets[i].x, n * offsets[i].y, 0);
1084 print_texture_string (bp->font_data, ":");
1096 /* Reads and returns a single Unicode character from the text client.
1098 static unsigned long
1099 read_unicode (ModeInfo *mi)
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;
1107 if (bp->clock_p || !bp->tc) abort();
1109 /* Fill the buffer with available input.
1111 i = strlen ((char *) bp->text);
1112 while (i < (end - bp->text))
1114 int c = textclient_getc (bp->tc);
1116 bp->text[i++] = (char) c;
1120 /* Pop 1-4 bytes from the front of the buffer and extract a UTF8 character.
1122 L = utf8_decode (bp->text, i, &uc);
1125 int j = end - bp->text - L;
1126 memmove (bp->text, bp->text + L, j);
1136 /* Given a Unicode character, finds the corresponding index on the spool,
1137 if any. Returns 0 if not found.
1140 find_index (ModeInfo *mi, flapper *f, long uc)
1143 int L = utf8_encode (uc, string, sizeof(string) - 1);
1145 if (L <= 0) return 0;
1147 for (i = 0; i < f->spool_size; i++)
1149 if (!strcmp (string, f->spool[i]))
1156 /* Read input from the text client and populate the spool with it.
1159 fill_targets (ModeInfo *mi)
1161 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
1168 time_t now = time ((time_t *) 0);
1169 struct tm *tm = localtime (&now);
1170 const char *fmt = (bp->clock_p == 24
1174 strftime (buf, sizeof(buf)-1, fmt, tm);
1175 if (bp->clock_p == 12 && buf[0] == '0')
1178 for (i = 0; i < strlen(buf); i++)
1180 flapper *f = &bp->flappers[i];
1181 f->target_index = find_index (mi, f, buf[i]);
1183 for (; i < grid_width * grid_height; i++)
1185 flapper *f = &bp->flappers[i];
1186 f->target_index = find_index (mi, f, ' ');
1191 for (y = 0; y < grid_height; y++)
1194 for (x = 0; x < grid_width; x++)
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')
1201 else if (uc == 12) /* ^L */
1204 /* Convert Unicode to the closest Latin1 equivalent. */
1207 Bool ascii_p = (f->spool != latin1_spool);
1208 unsigned char s[5], *s2;
1209 int L = utf8_encode (uc, (char *) s, sizeof(s));
1211 s2 = (unsigned char *) utf8_to_latin1 ((char *) s, ascii_p);
1213 if (s2[0] < 128) /* ASCII */
1215 else /* Latin1 -> UTF8 -> Unicode */
1217 s[0] = (s2[0] > 0xBF ? 0xC3 : 0xC2);
1218 s[1] = s2[0] & (s2[0] > 0xBF ? 0xBF : 0xFF);
1220 utf8_decode (s, 2, &uc);
1226 /* Upcase ASCII. Upcasing Unicrud would be rocket surgery. */
1227 if (uc >= 'a' && uc <= 'z') uc += ('A'-'a');
1229 f->target_index = find_index (mi, f, uc);
1231 f->sticky = (((random() % 20) == 0)
1232 ? 0.05 + frand(0.1) + frand(0.1)
1238 for (y = 0; y < grid_height; y++)
1240 fprintf (stderr, "# ");
1241 for (x = 0; x < grid_width; x++)
1243 int i = y * grid_width + x;
1244 flapper *f = &bp->flappers[i];
1245 fprintf(stderr, "%s", bp->spool[f->target_index]);
1247 fprintf (stderr, " #\n");
1249 fprintf (stderr, "\n");
1255 draw_flappers (ModeInfo *mi, Bool text_p)
1257 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
1261 for (y = 0; y < grid_height; y++)
1262 for (x = 0; x < grid_width; x++)
1264 int i = (grid_height - y - 1) * grid_width + x;
1265 flapper *f = &bp->flappers[i];
1271 if (x >= 2) xx += COLON_WIDTH;
1272 if (x >= 4) xx += COLON_WIDTH;
1273 if (x >= 6) xx += COLON_WIDTH;
1280 glTranslatef (xx, yy, 0);
1281 mi->polygon_count += draw_flapper (mi, f, text_p);
1283 if (text_p && bp->clock_p && (x == 2 || x == 4))
1284 mi->polygon_count += draw_colon (mi);
1290 tick_flapper (mi, f);
1291 if (f->current_index != f->target_index)
1296 if (text_p && !running)
1300 else if (bp->linger)
1308 /* Base of 1 second, plus 1 second for every 25 characters.
1309 Also multiply by speed? */
1311 if (!bp->first_time_p)
1312 bp->linger += (grid_width * grid_height * 1.2);
1313 bp->first_time_p = False;
1320 draw_splitflap (ModeInfo *mi)
1322 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
1323 Display *dpy = MI_DISPLAY(mi);
1324 Window window = MI_WINDOW(mi);
1326 if (!bp->glx_context)
1329 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
1331 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1334 glRotatef(current_device_rotation(), 0, 0, 1);
1336 glScalef (0.1, 0.1, 0.1); /* because of gluLookAt */
1340 get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
1341 glTranslatef((x - 0.5) * 8,
1345 gltrackball_rotate (bp->trackball);
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);
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);
1366 /* Fit the whole grid on the screen */
1368 GLfloat r = MI_HEIGHT(mi) / (GLfloat) MI_WIDTH(mi);
1369 int cells = (grid_width > grid_height
1374 s *= 2; /* #### What. Why is this necessary? */
1380 mi->polygon_count = 0;
1381 mi->polygon_count += draw_component (mi, SPLITFLAP_OUTER_FRAME);
1384 GLfloat xoff = (bp->clock_p == 12 ? COLON_WIDTH * 3 :
1385 bp->clock_p == 24 ? COLON_WIDTH * 2 :
1387 glTranslatef (1 - (grid_width + xoff), 1 - grid_height, 0);
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);
1397 if (mi->fps_p) do_fps (mi);
1400 glXSwapBuffers(dpy, window);
1404 release_splitflap (ModeInfo *mi)
1406 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
1408 textclient_close (bp->tc);
1409 /* #### bp->texinfo */
1412 XSCREENSAVER_MODULE ("SplitFlap", splitflap)