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 glClear(GL_COLOR_BUFFER_BIT);
221 splitflap_handle_event (ModeInfo *mi, XEvent *event)
223 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
225 if (gltrackball_event_handler (event, bp->trackball,
226 MI_WIDTH (mi), MI_HEIGHT (mi),
235 init_textures (ModeInfo *mi)
237 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
239 const char * const *spool = latin1_spool;
240 int max = countof(latin1_spool);
242 bp->texinfo = (texinfo *) calloc (max+1, sizeof(*bp->texinfo));
243 texture_string_metrics (bp->font_data, "", 0, &bp->ascent, &bp->descent);
245 for (i = 0; i < max; i++)
247 texinfo *ti = &bp->texinfo[i];
248 glGenTextures (1, &ti->texid);
249 glBindTexture (GL_TEXTURE_2D, ti->texid);
253 /* fprintf(stderr, "%d \\%03o\\%03o %s\n", i,
254 (unsigned char) ti->text[0],
255 (unsigned char) ti->text[1],
258 string_to_texture (bp->font_data, ti->text, &ti->metrics,
259 &ti->tex_width, &ti->tex_height);
261 bp->texinfo_size = i;
263 glBindTexture (GL_TEXTURE_2D, 0);
268 parse_color (ModeInfo *mi, char *key, GLfloat color[4])
271 char *string = get_string_resource (mi->dpy, key, "Color");
272 if (!XParseColor (mi->dpy, mi->xgwa.colormap, string, &xcolor))
274 fprintf (stderr, "%s: unparsable color in %s: %s\n", progname,
279 color[0] = xcolor.red / 65536.0;
280 color[1] = xcolor.green / 65536.0;
281 color[2] = xcolor.blue / 65536.0;
286 static int draw_outer_frame (ModeInfo *mi);
289 init_splitflap (ModeInfo *mi)
291 splitflap_configuration *bp;
292 int wire = MI_IS_WIREFRAME(mi);
295 bps = (splitflap_configuration *)
296 calloc (MI_NUM_SCREENS(mi), sizeof (splitflap_configuration));
298 fprintf(stderr, "%s: out of memory\n", progname);
303 bp = &bps[MI_SCREEN(mi)];
304 bp->glx_context = init_GL(mi);
305 reshape_splitflap (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
307 bp->first_time_p = True;
309 if (!mode_str || !*mode_str || !strcasecmp(mode_str, "text"))
313 else if (!strcasecmp (mode_str, "clock") ||
314 !strcasecmp (mode_str, "clock12"))
320 else if (!strcasecmp (mode_str, "clock24"))
329 "%s: `mode' must be text, clock12 or clock24: not `%s'\n",
336 bp->tc = textclient_open (MI_DISPLAY (mi));
340 textclient_reshape (bp->tc,
341 grid_width, grid_height,
342 grid_width, grid_height,
349 glShadeModel(GL_SMOOTH);
351 glEnable(GL_DEPTH_TEST);
352 glEnable(GL_NORMALIZE);
353 glEnable(GL_CULL_FACE);
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};
363 glEnable(GL_LIGHTING);
365 glEnable(GL_DEPTH_TEST);
366 glEnable(GL_CULL_FACE);
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);
373 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
378 double spin_speed = 0.5;
379 double wander_speed = 0.005;
380 double tilt_speed = 0.001;
381 double spin_accel = 0.5;
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') ;
393 "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
400 bp->rot = make_rotator (bp->spinx ? spin_speed : 0,
401 bp->spiny ? spin_speed : 0,
402 bp->spinz ? spin_speed : 0,
404 do_wander ? wander_speed : 0,
406 bp->rot2 = (face_front_p
407 ? make_rotator (0, 0, 0, 0, tilt_speed, True)
409 bp->trackball = gltrackball_init (False);
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);
416 parse_color (mi, "textColor", bp->text_color);
417 for (i = 0; i < countof(all_objs); i++)
419 const struct gllist *gll = *all_objs[i];
421 GLfloat spec[4] = {0.4, 0.4, 0.4, 1.0};
422 GLfloat shiny = 80; /* 0-128 */
424 glNewList (bp->dlists[i], GL_COMPILE);
426 glMatrixMode(GL_MODELVIEW);
428 glMatrixMode(GL_TEXTURE);
430 glMatrixMode(GL_MODELVIEW);
432 glRotatef (-90, 1, 0, 0);
434 glBindTexture (GL_TEXTURE_2D, 0);
437 case SPLITFLAP_QUARTER_FRAME:
440 case SPLITFLAP_OUTER_FRAME:
443 case SPLITFLAP_DISC_QUARTER:
444 key = (wire ? "frameColor" : "discColor");
446 case SPLITFLAP_FIN_EDGE_HALF:
447 case SPLITFLAP_FIN_FACE_HALF:
454 parse_color (mi, key, bp->component_colors[i]);
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;
461 glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, spec);
462 glMaterialf (GL_FRONT_AND_BACK, GL_SHININESS, shiny);
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);
472 renderList (gll, wire);
476 glMatrixMode(GL_TEXTURE);
478 glMatrixMode(GL_MODELVIEW);
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,
489 for (i = 0; i < grid_width * grid_height; i++)
491 flapper *f = &bp->flappers[i];
495 f->spool = ascii_spool;
496 f->spool_size = countof (ascii_spool);
502 if (bp->clock_p == 12)
504 f->spool = digit_s1_spool;
505 f->spool_size = countof (digit_s1_spool);
509 f->spool = digit_01_spool;
510 f->spool_size = countof (digit_01_spool);
513 case 1: case 3: case 5:
514 f->spool = digit_spool;
515 f->spool_size = countof (digit_spool);
518 f->spool = digit_05_spool;
519 f->spool_size = countof (digit_05_spool);
523 f->spool_size = countof (ap_spool);
527 f->spool_size = countof (m_spool);
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)
542 bp->font_data = load_texture_font (mi->dpy, "flapFont");
545 reshape_splitflap (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
550 draw_component (ModeInfo *mi, int i)
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;
561 draw_frame_quarter (ModeInfo *mi, flapper *f)
565 count += draw_component (mi, SPLITFLAP_QUARTER_FRAME);
571 draw_disc_quarter (ModeInfo *mi, flapper *f)
575 count += draw_component (mi, SPLITFLAP_DISC_QUARTER);
581 draw_fin_edge_half (ModeInfo *mi, flapper *f)
585 count += draw_component (mi, SPLITFLAP_FIN_EDGE_HALF);
591 draw_fin_face_half (ModeInfo *mi, flapper *f)
594 if (MI_IS_WIREFRAME(mi)) return 0;
596 count += draw_component (mi, SPLITFLAP_FIN_FACE_HALF);
603 draw_frame (ModeInfo *mi, flapper *f)
609 glFrontFace (GL_CCW);
610 count += draw_frame_quarter (mi, f);
611 count += draw_disc_quarter (mi, f);
615 count += draw_frame_quarter (mi, f);
616 count += draw_disc_quarter (mi, f);
618 glScalef ( 1, -1, 1);
619 glFrontFace (GL_CCW);
620 count += draw_frame_quarter (mi, f);
621 count += draw_disc_quarter (mi, f);
625 count += draw_frame_quarter (mi, f);
626 count += draw_disc_quarter (mi, f);
634 draw_fin_text_quad (ModeInfo *mi, flapper *f, int index, Bool top_p)
636 int wire = MI_IS_WIREFRAME(mi);
637 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
640 27 ; descends too far
642 59 [ descends too far
643 79 A^ is taller than the font
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 */
651 int lh = bp->ascent + bp->descent;
653 GLfloat qx0, qy0, qx1, qy1;
654 GLfloat tx0, ty0, tx1, ty1;
656 int tex_width, tex_height;
659 for (i = 0; i < bp->texinfo_size; i++)
661 ti = &bp->texinfo[i];
662 if (!strcmp (f->spool[index], ti->text))
665 if (i >= bp->texinfo_size) abort();
667 overall = ti->metrics;
668 tex_width = ti->tex_width;
669 tex_height = ti->tex_height;
671 if (bp->ascent < overall.ascent)
672 /* WTF! Á 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;
678 glNormal3f (0, 0, 1);
679 glFrontFace (top_p ? GL_CCW : GL_CW);
683 glBindTexture (GL_TEXTURE_2D, ti->texid);
684 enable_texture_string_parameters();
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 */
693 glRotatef (180, 0, 0, 1);
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;
702 /* Center horizontally. */
703 qx0 -= (overall.rbearing - overall.lbearing) / 2.0;
704 qx1 -= (overall.rbearing - overall.lbearing) / 2.0;
707 /* Move origin to below font descenders. */
711 /* Center vertically. */
712 qy0 -= (bp->ascent + bp->descent) / 2.0;
713 qy1 -= (bp->ascent + bp->descent) / 2.0;
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?
721 GLfloat off = bp->descent / 3.0;
722 GLfloat max = bp->descent - off;
723 if (overall.descent > max)
724 off = max - overall.descent;
730 # endif /* !HAVE_COCOA */
732 /* Attach the texture to the quad. */
735 tx1 = (overall.rbearing - overall.lbearing) / (GLfloat) tex_width;
736 ty0 = (overall.ascent + overall.descent) / (GLfloat) tex_height;
738 /* Convert from font ascent/descent to character ascent/descent. */
740 /* Flip texture horizontally on bottom panel. */
749 /* Cut the character in half, truncating just above the split line. */
768 r0 = (qy0 - oqy0) / (oqy1 - oqy0);
769 r1 = (qy1 - oqy1) / (oqy1 - oqy0);
770 ty0 -= r0 * (ty0 - ty1);
771 ty1 -= r1 * (ty0 - ty1);
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);
786 glDisable (GL_BLEND);
787 glEnable (GL_LIGHTING);
788 glDisable (GL_TEXTURE_2D);
794 draw_fin (ModeInfo *mi, flapper *f, int front_index, int back_index,
801 glFrontFace (GL_CCW);
804 count += draw_fin_edge_half (mi, f);
806 if (front_index >= 0)
810 draw_fin_text_quad (mi, f, front_index, True);
814 count += draw_fin_face_half (mi, f);
821 count += draw_fin_edge_half (mi, f);
822 if (front_index >= 0)
823 count += draw_fin_face_half (mi, f);
828 glRotatef (180, 0, 1, 0);
831 draw_fin_text_quad (mi, f, back_index, False);
836 count += draw_fin_face_half (mi, f);
838 glFrontFace (GL_CCW);
839 count += draw_fin_face_half (mi, f);
848 /* The case holding the grid of flappers.
851 draw_outer_frame (ModeInfo *mi)
853 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
855 GLfloat w = grid_width;
856 GLfloat h = grid_height;
859 if (bp->clock_p == 12)
860 w += COLON_WIDTH * 3;
861 else if (bp->clock_p == 24)
862 w += COLON_WIDTH * 2;
867 if (bp->clock_p) w += 0.25;
871 if (MI_IS_WIREFRAME(mi))
874 glFrontFace (GL_CCW);
876 glTranslatef (0, 1.03, 0);
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);
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);
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);
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);
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);
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);
930 tick_flapper (ModeInfo *mi, flapper *f)
932 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
933 double prev = f->current_index;
934 Bool wrapped_p = False;
936 if (bp->button_down_p) return;
937 if (f->current_index == f->target_index)
940 f->current_index += speed * 0.35; /* turn the crank */
942 while (f->current_index > f->spool_size)
944 f->current_index -= f->spool_size;
948 if (f->current_index < 0) abort();
950 if ((prev < f->target_index || wrapped_p) &&
951 f->current_index > f->target_index) /* just overshot */
952 f->current_index = f->target_index;
956 #define MOD(M,N) (((M)+(N)) % (N)) /* Works with negatives */
959 draw_flapper (ModeInfo *mi, flapper *f, Bool text_p)
961 int prev_index = floor (f->current_index);
962 int next_index = MOD (prev_index+1, f->spool_size);
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;
972 if (f->missing >= 0 &&
973 MOD (prev_index, f->spool_size) == f->missing)
980 next_index = prev_index;
983 count += draw_frame (mi, f);
985 /* Top flap, flat: top half of target char */
986 if (!moving_p || !text_p || r > epsilon)
990 if (p2 == f->missing)
991 p2 = MOD (p2+1, f->spool_size);
993 count += draw_fin (mi, f, p2, -1, text_p);
996 /* Bottom flap, flat: bottom half of prev char */
997 if (!moving_p || !text_p || r < 1 - epsilon)
1001 if (!moving_p && sticky)
1002 p2 = MOD (p2-1, f->spool_size);
1004 if (f->missing >= 0 &&
1005 p2 == MOD (f->missing+1, f->spool_size))
1006 p2 = MOD (p2-1, f->spool_size);
1009 glRotatef (180, 1, 0, 0);
1010 count += draw_fin (mi, f, -1, p2, text_p);
1014 /* Moving flap, front: top half of prev char */
1015 /* Moving flap, back: bottom half of target char */
1016 if (moving_p || sticky)
1020 if (sticky && r > 1 - sticky)
1023 glRotatef (r * 180, 1, 0, 0);
1024 count += draw_fin (mi, f, prev_index, next_index, text_p);
1033 draw_colon (ModeInfo *mi)
1035 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
1036 GLfloat s = 1.0 / (bp->ascent + bp->descent);
1041 texture_string_metrics (bp->font_data, ":", &m, 0, 0);
1047 glTranslatef (-(1 + COLON_WIDTH), 0, 0);
1050 glTranslatef (-m.lbearing - (m.rbearing - m.lbearing)/2,
1051 -(m.ascent + m.descent) / 2,
1054 glEnable (GL_TEXTURE_2D);
1056 /* draw the text five times, to give it a border. */
1058 const XPoint offsets[] = {{ -1, -1 },
1066 glColor3f (0, 0, 0);
1067 for (i = 0; i < countof(offsets); i++)
1070 if (offsets[i].x == 0)
1072 glColor4fv (bp->text_color);
1073 glTranslatef (0, 0, z * 2);
1075 glTranslatef (n * offsets[i].x, n * offsets[i].y, 0);
1076 print_texture_string (bp->font_data, ":");
1088 /* Reads and returns a single Unicode character from the text client.
1090 static unsigned long
1091 read_unicode (ModeInfo *mi)
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;
1099 if (bp->clock_p || !bp->tc) abort();
1101 /* Fill the buffer with available input.
1103 i = strlen ((char *) bp->text);
1104 while (i < (end - bp->text))
1106 int c = textclient_getc (bp->tc);
1108 bp->text[i++] = (char) c;
1112 /* Pop 1-4 bytes from the front of the buffer and extract a UTF8 character.
1114 L = utf8_decode (bp->text, i, &uc);
1117 int j = end - bp->text - L;
1118 memmove (bp->text, bp->text + L, j);
1128 /* Given a Unicode character, finds the corresponding index on the spool,
1129 if any. Returns 0 if not found.
1132 find_index (ModeInfo *mi, flapper *f, long uc)
1135 int L = utf8_encode (uc, string, sizeof(string) - 1);
1137 if (L <= 0) return 0;
1139 for (i = 0; i < f->spool_size; i++)
1141 if (!strcmp (string, f->spool[i]))
1148 /* Read input from the text client and populate the spool with it.
1151 fill_targets (ModeInfo *mi)
1153 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
1160 time_t now = time ((time_t *) 0);
1161 struct tm *tm = localtime (&now);
1162 const char *fmt = (bp->clock_p == 24
1166 strftime (buf, sizeof(buf)-1, fmt, tm);
1167 if (bp->clock_p == 12 && buf[0] == '0')
1170 for (i = 0; i < strlen(buf); i++)
1172 flapper *f = &bp->flappers[i];
1173 f->target_index = find_index (mi, f, buf[i]);
1175 for (; i < grid_width * grid_height; i++)
1177 flapper *f = &bp->flappers[i];
1178 f->target_index = find_index (mi, f, ' ');
1183 for (y = 0; y < grid_height; y++)
1186 for (x = 0; x < grid_width; x++)
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')
1193 else if (uc == 12) /* ^L */
1196 /* Convert Unicode to the closest Latin1 equivalent. */
1199 Bool ascii_p = (f->spool != latin1_spool);
1200 unsigned char s[5], *s2;
1201 int L = utf8_encode (uc, (char *) s, sizeof(s));
1203 s2 = (unsigned char *) utf8_to_latin1 ((char *) s, ascii_p);
1205 if (s2[0] < 128) /* ASCII */
1207 else /* Latin1 -> UTF8 -> Unicode */
1209 s[0] = (s2[0] > 0xBF ? 0xC3 : 0xC2);
1210 s[1] = s2[0] & (s2[0] > 0xBF ? 0xBF : 0xFF);
1212 utf8_decode (s, 2, &uc);
1218 /* Upcase ASCII. Upcasing Unicrud would be rocket surgery. */
1219 if (uc >= 'a' && uc <= 'z') uc += ('A'-'a');
1221 f->target_index = find_index (mi, f, uc);
1223 f->sticky = (((random() % 20) == 0)
1224 ? 0.05 + frand(0.1) + frand(0.1)
1230 for (y = 0; y < grid_height; y++)
1232 fprintf (stderr, "# ");
1233 for (x = 0; x < grid_width; x++)
1235 int i = y * grid_width + x;
1236 flapper *f = &bp->flappers[i];
1237 fprintf(stderr, "%s", bp->spool[f->target_index]);
1239 fprintf (stderr, " #\n");
1241 fprintf (stderr, "\n");
1247 draw_flappers (ModeInfo *mi, Bool text_p)
1249 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
1253 for (y = 0; y < grid_height; y++)
1254 for (x = 0; x < grid_width; x++)
1256 int i = (grid_height - y - 1) * grid_width + x;
1257 flapper *f = &bp->flappers[i];
1263 if (x >= 2) xx += COLON_WIDTH;
1264 if (x >= 4) xx += COLON_WIDTH;
1265 if (x >= 6) xx += COLON_WIDTH;
1272 glTranslatef (xx, yy, 0);
1273 mi->polygon_count += draw_flapper (mi, f, text_p);
1275 if (text_p && bp->clock_p && (x == 2 || x == 4))
1276 mi->polygon_count += draw_colon (mi);
1282 tick_flapper (mi, f);
1283 if (f->current_index != f->target_index)
1288 if (text_p && !running)
1292 else if (bp->linger)
1300 /* Base of 1 second, plus 1 second for every 25 characters.
1301 Also multiply by speed? */
1303 if (!bp->first_time_p)
1304 bp->linger += (grid_width * grid_height * 1.2);
1305 bp->first_time_p = False;
1312 draw_splitflap (ModeInfo *mi)
1314 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
1315 Display *dpy = MI_DISPLAY(mi);
1316 Window window = MI_WINDOW(mi);
1318 if (!bp->glx_context)
1321 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
1323 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1326 glRotatef(current_device_rotation(), 0, 0, 1);
1328 glScalef (0.1, 0.1, 0.1); /* because of gluLookAt */
1332 get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
1333 glTranslatef((x - 0.5) * 8,
1337 gltrackball_rotate (bp->trackball);
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);
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);
1358 /* Fit the whole grid on the screen */
1360 GLfloat r = MI_HEIGHT(mi) / (GLfloat) MI_WIDTH(mi);
1361 int cells = (grid_width > grid_height
1366 s *= 2; /* #### What. Why is this necessary? */
1372 mi->polygon_count = 0;
1373 mi->polygon_count += draw_component (mi, SPLITFLAP_OUTER_FRAME);
1376 GLfloat xoff = (bp->clock_p == 12 ? COLON_WIDTH * 3 :
1377 bp->clock_p == 24 ? COLON_WIDTH * 2 :
1379 glTranslatef (1 - (grid_width + xoff), 1 - grid_height, 0);
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);
1389 if (mi->fps_p) do_fps (mi);
1392 glXSwapBuffers(dpy, window);
1396 release_splitflap (ModeInfo *mi)
1398 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
1400 textclient_close (bp->tc);
1401 /* #### bp->texinfo */
1404 XSCREENSAVER_MODULE ("SplitFlap", splitflap)