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
30 # define release_splitflap 0
32 #define countof(x) (sizeof((x))/sizeof((*x)))
34 #define DEF_SPEED "1.0"
35 #define DEF_WIDTH "22"
36 #define DEF_HEIGHT "8"
37 #define DEF_SPIN "XYZ"
38 #define DEF_WANDER "True"
39 #define DEF_FACE_FRONT "True"
40 #define DEF_MODE "Text"
42 #include "xlockmore.h"
46 #ifdef USE_GL /* whole file */
48 #include "gltrackball.h"
50 #include "xpm-ximage.h"
52 #include "textclient.h"
56 extern const struct gllist
57 *splitflap_obj_box_quarter_frame, *splitflap_obj_disc_quarter,
58 *splitflap_obj_fin_edge_half, *splitflap_obj_fin_face_half;
59 static struct gllist *splitflap_obj_outer_frame = 0;
61 static const struct gllist * const *all_objs[] = {
62 &splitflap_obj_box_quarter_frame, &splitflap_obj_disc_quarter,
63 &splitflap_obj_fin_edge_half, &splitflap_obj_fin_face_half,
64 (const struct gllist * const *) &splitflap_obj_outer_frame
67 #define SPLITFLAP_QUARTER_FRAME 0
68 #define SPLITFLAP_DISC_QUARTER 1
69 #define SPLITFLAP_FIN_EDGE_HALF 2
70 #define SPLITFLAP_FIN_FACE_HALF 3
71 #define SPLITFLAP_OUTER_FRAME 4
73 #define COLON_WIDTH 0.5
76 int target_index; /* desired character */
77 double current_index; /* currently displayed, fractional */
78 GLfloat sticky; /* bottom fin doesn't fall all the way */
79 int missing; /* which fin has snapped off, or -1 */
80 const char * const *spool; /* chars available for display */
81 int spool_size; /* how many fins on the spool */
88 int tex_width, tex_height;
92 GLXContext *glx_context;
94 trackball_state *trackball;
96 Bool spinx, spiny, spinz;
102 GLfloat component_colors[countof(all_objs)][4];
103 GLfloat text_color[4];
105 flapper *flappers; /* grid_width * grid_height */
107 texture_font_data *font_data;
111 unsigned char text[5];
116 } splitflap_configuration;
118 static const char * const digit_s1_spool[] = { " ", "1" };
119 static const char * const digit_01_spool[] = { "0", "1" };
120 static const char * const ap_spool[] = { "A", "P" };
121 static const char * const m_spool[] = { "M" };
122 static const char * const digit_05_spool[] = { "0", "1", "2", "3", "4", "5" };
123 static const char * const digit_spool[] = {
124 "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"
127 static const char * const ascii_spool[] = {
128 " ", "!", "\"", "#", "$", "%", "&", "'",
129 "(", ")", "*", "+", ",", "-", ".", "/",
130 "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
131 ":", ";", "<", "=", ">", "?", "@",
132 "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
133 "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
134 "[", "\\", "]", "^", "_", "`", "{", "|", "}", "~",
138 /* If we include these, the flappers just take too long. It's boring. */
139 static const char * const latin1_spool[] = {
140 " ", "!", "\"", "#", "$", "%", "&", "'",
141 "(", ")", "*", "+", ",", "-", ".", "/",
142 "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
143 ":", ";", "<", "=", ">", "?", "@",
144 "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
145 "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
146 "[", "\\", "]", "^", "_", "`", "{", "|", "}", "~",
148 "\302\241", "\302\242", "\302\243", "\302\245",
149 "\302\247", "\302\251", "\302\265", "\302\266",
151 "\303\200", "\303\201", "\303\202", "\303\203",
152 "\303\204", "\303\205", "\303\206", "\303\207",
153 "\303\210", "\303\211", "\303\212", "\303\213",
154 "\303\214", "\303\215", "\303\216", "\303\217",
155 "\303\220", "\303\221", "\303\222", "\303\223",
156 "\303\224", "\303\225", "\303\226", "\303\230",
157 "\303\231", "\303\232", "\303\233", "\303\234",
158 "\303\235", "\303\236", "\303\237", "\303\267",
162 static splitflap_configuration *bps = NULL;
164 static GLfloat speed;
165 static int grid_width, grid_height;
166 static char *do_spin;
167 static Bool do_wander;
168 static Bool face_front_p;
169 static char *mode_str;
171 static XrmOptionDescRec opts[] = {
172 { "-speed", ".speed", XrmoptionSepArg, 0 },
173 { "-width", ".width", XrmoptionSepArg, 0 },
174 { "-height", ".height", XrmoptionSepArg, 0 },
175 { "-spin", ".spin", XrmoptionSepArg, 0 },
176 { "+spin", ".spin", XrmoptionNoArg, "" },
177 { "-wander", ".wander", XrmoptionNoArg, "True" },
178 { "+wander", ".wander", XrmoptionNoArg, "False" },
179 { "-front", ".faceFront", XrmoptionNoArg, "True" },
180 { "+front", ".faceFront", XrmoptionNoArg, "False" },
181 { "-mode", ".mode", XrmoptionSepArg, 0 },
184 static argtype vars[] = {
185 {&speed, "speed", "Speed", DEF_SPEED, t_Float},
186 {&grid_width, "width", "Width", DEF_WIDTH, t_Int},
187 {&grid_height, "height", "Height", DEF_HEIGHT, t_Int},
188 {&do_spin, "spin", "Spin", DEF_SPIN, t_String},
189 {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
190 {&face_front_p, "faceFront", "FaceFront", DEF_FACE_FRONT, t_Bool},
191 {&mode_str, "mode", "Mode", DEF_MODE, t_String},
194 ENTRYPOINT ModeSpecOpt splitflap_opts = {
195 countof(opts), opts, countof(vars), vars, NULL};
198 /* Window management, etc
201 reshape_splitflap (ModeInfo *mi, int width, int height)
203 GLfloat h = (GLfloat) height / (GLfloat) width;
205 glViewport (0, 0, (GLint) width, (GLint) height);
207 glMatrixMode(GL_PROJECTION);
209 gluPerspective (40.0, 1/h, 0.5, 25);
211 glMatrixMode(GL_MODELVIEW);
213 gluLookAt( 0, 0, 3, /* 10x lower than traditional, for better depth rez */
217 # ifdef HAVE_MOBILE /* Keep it the same relative size when rotated. */
219 int o = (int) current_device_rotation();
220 if (o != 0 && o != 180 && o != -180)
225 glClear(GL_COLOR_BUFFER_BIT);
230 splitflap_handle_event (ModeInfo *mi, XEvent *event)
232 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
234 if (gltrackball_event_handler (event, bp->trackball,
235 MI_WIDTH (mi), MI_HEIGHT (mi),
244 init_textures (ModeInfo *mi)
246 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
248 const char * const *spool = latin1_spool;
249 int max = countof(latin1_spool);
251 bp->texinfo = (texinfo *) calloc (max+1, sizeof(*bp->texinfo));
252 texture_string_metrics (bp->font_data, "", 0, &bp->ascent, &bp->descent);
254 for (i = 0; i < max; i++)
256 texinfo *ti = &bp->texinfo[i];
257 glGenTextures (1, &ti->texid);
258 glBindTexture (GL_TEXTURE_2D, ti->texid);
262 /* fprintf(stderr, "%d \\%03o\\%03o %s\n", i,
263 (unsigned char) ti->text[0],
264 (unsigned char) ti->text[1],
267 string_to_texture (bp->font_data, ti->text, &ti->metrics,
268 &ti->tex_width, &ti->tex_height);
270 bp->texinfo_size = i;
272 glBindTexture (GL_TEXTURE_2D, 0);
277 parse_color (ModeInfo *mi, char *key, GLfloat color[4])
280 char *string = get_string_resource (mi->dpy, key, "Color");
281 if (!XParseColor (mi->dpy, mi->xgwa.colormap, string, &xcolor))
283 fprintf (stderr, "%s: unparsable color in %s: %s\n", progname,
288 color[0] = xcolor.red / 65536.0;
289 color[1] = xcolor.green / 65536.0;
290 color[2] = xcolor.blue / 65536.0;
295 static int draw_outer_frame (ModeInfo *mi);
296 static void free_splitflap (ModeInfo *mi);
299 init_splitflap (ModeInfo *mi)
301 splitflap_configuration *bp;
302 int wire = MI_IS_WIREFRAME(mi);
304 MI_INIT (mi, bps, free_splitflap);
306 bp = &bps[MI_SCREEN(mi)];
307 bp->glx_context = init_GL(mi);
308 reshape_splitflap (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
310 bp->first_time_p = True;
312 if (!mode_str || !*mode_str || !strcasecmp(mode_str, "text"))
316 else if (!strcasecmp (mode_str, "clock") ||
317 !strcasecmp (mode_str, "clock12"))
323 else if (!strcasecmp (mode_str, "clock24"))
332 "%s: `mode' must be text, clock12 or clock24: not `%s'\n",
339 bp->tc = textclient_open (MI_DISPLAY (mi));
343 textclient_reshape (bp->tc,
344 grid_width, grid_height,
345 grid_width, grid_height,
352 glShadeModel(GL_SMOOTH);
354 glEnable(GL_DEPTH_TEST);
355 glEnable(GL_NORMALIZE);
356 glEnable(GL_CULL_FACE);
360 GLfloat pos[4] = {0.4, 0.2, 0.4, 0.0};
361 /* GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};*/
362 GLfloat amb[4] = {0.2, 0.2, 0.2, 1.0};
363 GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
364 GLfloat spc[4] = {1.0, 1.0, 1.0, 1.0};
366 glEnable(GL_LIGHTING);
368 glEnable(GL_DEPTH_TEST);
369 glEnable(GL_CULL_FACE);
371 glLightfv(GL_LIGHT0, GL_POSITION, pos);
372 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
373 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
374 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
376 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
381 double spin_speed = 0.5;
382 double wander_speed = 0.005;
383 double tilt_speed = 0.001;
384 double spin_accel = 0.5;
389 if (*s == 'x' || *s == 'X') bp->spinx = True;
390 else if (*s == 'y' || *s == 'Y') bp->spiny = True;
391 else if (*s == 'z' || *s == 'Z') bp->spinz = True;
392 else if (*s == '0') ;
396 "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
403 bp->rot = make_rotator (bp->spinx ? spin_speed : 0,
404 bp->spiny ? spin_speed : 0,
405 bp->spinz ? spin_speed : 0,
407 do_wander ? wander_speed : 0,
409 bp->rot2 = (face_front_p
410 ? make_rotator (0, 0, 0, 0, tilt_speed, True)
412 bp->trackball = gltrackball_init (False);
415 bp->dlists = (GLuint *) calloc (countof(all_objs)+1, sizeof(GLuint));
416 for (i = 0; i < countof(all_objs); i++)
417 bp->dlists[i] = glGenLists (1);
419 parse_color (mi, "textColor", bp->text_color);
420 for (i = 0; i < countof(all_objs); i++)
422 const struct gllist *gll = *all_objs[i];
424 GLfloat spec[4] = {0.4, 0.4, 0.4, 1.0};
425 GLfloat shiny = 80; /* 0-128 */
427 glNewList (bp->dlists[i], GL_COMPILE);
429 glMatrixMode(GL_MODELVIEW);
431 glMatrixMode(GL_TEXTURE);
433 glMatrixMode(GL_MODELVIEW);
435 glRotatef (-90, 1, 0, 0);
437 glBindTexture (GL_TEXTURE_2D, 0);
440 case SPLITFLAP_QUARTER_FRAME:
443 case SPLITFLAP_OUTER_FRAME:
446 case SPLITFLAP_DISC_QUARTER:
447 key = (wire ? "frameColor" : "discColor");
449 case SPLITFLAP_FIN_EDGE_HALF:
450 case SPLITFLAP_FIN_FACE_HALF:
457 parse_color (mi, key, bp->component_colors[i]);
459 if (wire && i == SPLITFLAP_FIN_EDGE_HALF)
460 bp->component_colors[i][0] =
461 bp->component_colors[i][1] =
462 bp->component_colors[i][2] = 0.7;
464 glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, spec);
465 glMaterialf (GL_FRONT_AND_BACK, GL_SHININESS, shiny);
468 case SPLITFLAP_OUTER_FRAME:
469 if (! splitflap_obj_outer_frame)
470 splitflap_obj_outer_frame =
471 (struct gllist *) calloc (1, sizeof(*splitflap_obj_outer_frame));
472 splitflap_obj_outer_frame->points = draw_outer_frame(mi);
475 renderList (gll, wire);
479 glMatrixMode(GL_TEXTURE);
481 glMatrixMode(GL_MODELVIEW);
487 if (grid_width < 1) grid_width = 1;
488 if (grid_height < 1) grid_height = 1;
489 bp->flappers = (flapper *) calloc (grid_width * grid_height,
492 for (i = 0; i < grid_width * grid_height; i++)
494 flapper *f = &bp->flappers[i];
498 f->spool = ascii_spool;
499 f->spool_size = countof (ascii_spool);
505 if (bp->clock_p == 12)
507 f->spool = digit_s1_spool;
508 f->spool_size = countof (digit_s1_spool);
512 f->spool = digit_01_spool;
513 f->spool_size = countof (digit_01_spool);
516 case 1: case 3: case 5:
517 f->spool = digit_spool;
518 f->spool_size = countof (digit_spool);
521 f->spool = digit_05_spool;
522 f->spool_size = countof (digit_05_spool);
526 f->spool_size = countof (ap_spool);
530 f->spool_size = countof (m_spool);
537 f->target_index = random() % f->spool_size;
538 /* f->target_index = 0; */
539 f->current_index = f->target_index;
540 f->missing = (((random() % 10) == 0)
541 ? (random() % f->spool_size)
545 bp->font_data = load_texture_font (mi->dpy, "flapFont");
548 reshape_splitflap (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
553 draw_component (ModeInfo *mi, int i)
555 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
556 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE,
557 bp->component_colors[i]);
558 glCallList (bp->dlists[i]);
559 return (*all_objs[i])->points / 3;
564 draw_frame_quarter (ModeInfo *mi, flapper *f)
568 count += draw_component (mi, SPLITFLAP_QUARTER_FRAME);
574 draw_disc_quarter (ModeInfo *mi, flapper *f)
578 count += draw_component (mi, SPLITFLAP_DISC_QUARTER);
584 draw_fin_edge_half (ModeInfo *mi, flapper *f)
588 count += draw_component (mi, SPLITFLAP_FIN_EDGE_HALF);
594 draw_fin_face_half (ModeInfo *mi, flapper *f)
597 if (MI_IS_WIREFRAME(mi)) return 0;
599 count += draw_component (mi, SPLITFLAP_FIN_FACE_HALF);
606 draw_frame (ModeInfo *mi, flapper *f)
612 glFrontFace (GL_CCW);
613 count += draw_frame_quarter (mi, f);
614 count += draw_disc_quarter (mi, f);
618 count += draw_frame_quarter (mi, f);
619 count += draw_disc_quarter (mi, f);
621 glScalef ( 1, -1, 1);
622 glFrontFace (GL_CCW);
623 count += draw_frame_quarter (mi, f);
624 count += draw_disc_quarter (mi, f);
628 count += draw_frame_quarter (mi, f);
629 count += draw_disc_quarter (mi, f);
637 draw_fin_text_quad (ModeInfo *mi, flapper *f, int index, Bool top_p)
639 int wire = MI_IS_WIREFRAME(mi);
640 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
643 27 ; descends too far
645 59 [ descends too far
646 79 A^ is taller than the font
650 GLfloat z = 0.035; /* Lifted off the surface by this distance */
651 GLfloat bot = 0.013; /* Distance away from the mid gutter */
652 GLfloat scale = 1.8; /* Scale to fill the panel */
654 int lh = bp->ascent + bp->descent;
656 GLfloat qx0, qy0, qx1, qy1;
657 GLfloat tx0, ty0, tx1, ty1;
659 int tex_width, tex_height;
662 for (i = 0; i < bp->texinfo_size; i++)
664 ti = &bp->texinfo[i];
665 if (!strcmp (f->spool[index], ti->text))
668 if (i >= bp->texinfo_size) abort();
670 overall = ti->metrics;
671 tex_width = ti->tex_width;
672 tex_height = ti->tex_height;
674 if (bp->ascent < overall.ascent)
675 /* WTF! Á has a higher ascent than the font itself!
676 Scale it down so that it doesn't overlap the fin. */
677 scale *= bp->ascent / (GLfloat) overall.ascent * 0.98;
681 glNormal3f (0, 0, 1);
682 glFrontFace (top_p ? GL_CCW : GL_CW);
686 glBindTexture (GL_TEXTURE_2D, ti->texid);
687 enable_texture_string_parameters();
690 glTranslatef (0, 0, z); /* Move to just above the surface */
691 glScalef (1.0 / lh, 1.0 / lh, 1); /* Scale to font pixel coordinates */
692 glScalef (scale, scale, 1); /* Fill the panel with the font */
696 glRotatef (180, 0, 0, 1);
699 /* Position the XCharStruct origin at 0,0 in the scene. */
700 qx0 = -overall.lbearing;
701 qy0 = -overall.descent;
702 qx1 = overall.rbearing;
703 qy1 = overall.ascent;
705 /* Center horizontally. */
706 qx0 -= (overall.rbearing - overall.lbearing) / 2.0;
707 qx1 -= (overall.rbearing - overall.lbearing) / 2.0;
710 /* Move origin to below font descenders. */
714 /* Center vertically. */
715 qy0 -= (bp->ascent + bp->descent) / 2.0;
716 qy1 -= (bp->ascent + bp->descent) / 2.0;
718 /* Move the descenders down a bit, if there's room.
719 This means that weirdos like [ and $ might not be on the baseline.
720 #### This looks good with X11 fonts but bad with MacOS fonts. WTF?
724 GLfloat off = bp->descent / 3.0;
725 GLfloat max = bp->descent - off;
726 if (overall.descent > max)
727 off = max - overall.descent;
733 # endif /* !HAVE_COCOA */
735 /* Attach the texture to the quad. */
738 tx1 = (overall.rbearing - overall.lbearing) / (GLfloat) tex_width;
739 ty0 = (overall.ascent + overall.descent) / (GLfloat) tex_height;
741 /* Convert from font ascent/descent to character ascent/descent. */
743 /* Flip texture horizontally on bottom panel. */
752 /* Cut the character in half, truncating just above the split line. */
771 r0 = (qy0 - oqy0) / (oqy1 - oqy0);
772 r1 = (qy1 - oqy1) / (oqy1 - oqy0);
773 ty0 -= r0 * (ty0 - ty1);
774 ty1 -= r1 * (ty0 - ty1);
777 glColor4fv (bp->text_color);
778 glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
779 glTexCoord2f (tx0, ty0); glVertex3f (qx0, qy0, 0);
780 glTexCoord2f (tx1, ty0); glVertex3f (qx1, qy0, 0);
781 glTexCoord2f (tx1, ty1); glVertex3f (qx1, qy1, 0);
782 glTexCoord2f (tx0, ty1); glVertex3f (qx0, qy1, 0);
789 glDisable (GL_BLEND);
790 glEnable (GL_LIGHTING);
791 glDisable (GL_TEXTURE_2D);
797 draw_fin (ModeInfo *mi, flapper *f, int front_index, int back_index,
804 glFrontFace (GL_CCW);
807 count += draw_fin_edge_half (mi, f);
809 if (front_index >= 0)
813 draw_fin_text_quad (mi, f, front_index, True);
817 count += draw_fin_face_half (mi, f);
824 count += draw_fin_edge_half (mi, f);
825 if (front_index >= 0)
826 count += draw_fin_face_half (mi, f);
831 glRotatef (180, 0, 1, 0);
834 draw_fin_text_quad (mi, f, back_index, False);
839 count += draw_fin_face_half (mi, f);
841 glFrontFace (GL_CCW);
842 count += draw_fin_face_half (mi, f);
851 /* The case holding the grid of flappers.
854 draw_outer_frame (ModeInfo *mi)
856 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
858 GLfloat w = grid_width;
859 GLfloat h = grid_height;
862 if (bp->clock_p == 12)
863 w += COLON_WIDTH * 3;
864 else if (bp->clock_p == 24)
865 w += COLON_WIDTH * 2;
870 if (bp->clock_p) w += 0.25;
874 if (MI_IS_WIREFRAME(mi))
877 glFrontFace (GL_CCW);
879 glTranslatef (0, 1.03, 0);
883 glNormal3f ( 0, 1, 0); /* back */
884 glVertex3f (-w, d, h);
885 glVertex3f ( w, d, h);
886 glVertex3f ( w, d, -h);
887 glVertex3f (-w, d, -h);
890 glNormal3f ( 0, -1, 0); /* front */
891 glVertex3f (-w, -d, -h);
892 glVertex3f ( w, -d, -h);
893 glVertex3f ( w, -d, h);
894 glVertex3f (-w, -d, h);
897 glNormal3f ( 0, 0, 1); /* top */
898 glVertex3f (-w, -d, h);
899 glVertex3f ( w, -d, h);
900 glVertex3f ( w, d, h);
901 glVertex3f (-w, d, h);
904 glNormal3f ( 0, 0, -1); /* bottom */
905 glVertex3f (-w, d, -h);
906 glVertex3f ( w, d, -h);
907 glVertex3f ( w, -d, -h);
908 glVertex3f (-w, -d, -h);
911 glNormal3f ( 1, 0, 0); /* left */
912 glVertex3f ( w, -d, h);
913 glVertex3f ( w, -d, -h);
914 glVertex3f ( w, d, -h);
915 glVertex3f ( w, d, h);
918 glNormal3f (-1, 0, 0); /* right */
919 glVertex3f (-w, -d, -h);
920 glVertex3f (-w, -d, h);
921 glVertex3f (-w, d, h);
922 glVertex3f (-w, d, -h);
933 tick_flapper (ModeInfo *mi, flapper *f)
935 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
936 double prev = f->current_index;
937 Bool wrapped_p = False;
939 if (bp->button_down_p) return;
940 if (f->current_index == f->target_index)
943 f->current_index += speed * 0.35; /* turn the crank */
945 while (f->current_index > f->spool_size)
947 f->current_index -= f->spool_size;
951 if (f->current_index < 0) abort();
953 if ((prev < f->target_index || wrapped_p) &&
954 f->current_index > f->target_index) /* just overshot */
955 f->current_index = f->target_index;
959 #define MOD(M,N) (((M)+(N)) % (N)) /* Works with negatives */
962 draw_flapper (ModeInfo *mi, flapper *f, Bool text_p)
964 int prev_index = floor (f->current_index);
965 int next_index = MOD (prev_index+1, f->spool_size);
967 GLfloat epsilon = 0.02;
968 GLfloat r = f->current_index - prev_index;
969 Bool moving_p = (r > 0 && r < 1);
970 GLfloat sticky = f->sticky;
975 if (f->missing >= 0 &&
976 MOD (prev_index, f->spool_size) == f->missing)
983 next_index = prev_index;
986 count += draw_frame (mi, f);
988 /* Top flap, flat: top half of target char */
989 if (!moving_p || !text_p || r > epsilon)
993 if (p2 == f->missing)
994 p2 = MOD (p2+1, f->spool_size);
996 count += draw_fin (mi, f, p2, -1, text_p);
999 /* Bottom flap, flat: bottom half of prev char */
1000 if (!moving_p || !text_p || r < 1 - epsilon)
1002 int p2 = prev_index;
1004 if (!moving_p && sticky)
1005 p2 = MOD (p2-1, f->spool_size);
1007 if (f->missing >= 0 &&
1008 p2 == MOD (f->missing+1, f->spool_size))
1009 p2 = MOD (p2-1, f->spool_size);
1012 glRotatef (180, 1, 0, 0);
1013 count += draw_fin (mi, f, -1, p2, text_p);
1017 /* Moving flap, front: top half of prev char */
1018 /* Moving flap, back: bottom half of target char */
1019 if (moving_p || sticky)
1023 if (sticky && r > 1 - sticky)
1026 glRotatef (r * 180, 1, 0, 0);
1027 count += draw_fin (mi, f, prev_index, next_index, text_p);
1036 draw_colon (ModeInfo *mi)
1038 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
1039 GLfloat s = 1.0 / (bp->ascent + bp->descent);
1044 texture_string_metrics (bp->font_data, ":", &m, 0, 0);
1050 glTranslatef (-(1 + COLON_WIDTH), 0, 0);
1053 glTranslatef (-m.lbearing - (m.rbearing - m.lbearing)/2,
1054 -(m.ascent + m.descent) / 2,
1057 glEnable (GL_TEXTURE_2D);
1059 /* draw the text five times, to give it a border. */
1061 const XPoint offsets[] = {{ -1, -1 },
1069 glColor3f (0, 0, 0);
1070 for (i = 0; i < countof(offsets); i++)
1073 if (offsets[i].x == 0)
1075 glColor4fv (bp->text_color);
1076 glTranslatef (0, 0, z * 2);
1078 glTranslatef (n * offsets[i].x, n * offsets[i].y, 0);
1079 print_texture_string (bp->font_data, ":");
1091 /* Reads and returns a single Unicode character from the text client.
1093 static unsigned long
1094 read_unicode (ModeInfo *mi)
1096 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
1097 const unsigned char *end = bp->text + sizeof(bp->text) - 1; /* 4 bytes */
1098 unsigned long uc = 0;
1102 if (bp->clock_p || !bp->tc) abort();
1104 /* Fill the buffer with available input.
1106 i = strlen ((char *) bp->text);
1107 while (i < (end - bp->text))
1109 int c = textclient_getc (bp->tc);
1111 bp->text[i++] = (char) c;
1115 /* Pop 1-4 bytes from the front of the buffer and extract a UTF8 character.
1117 L = utf8_decode (bp->text, i, &uc);
1120 int j = end - bp->text - L;
1121 memmove (bp->text, bp->text + L, j);
1131 /* Given a Unicode character, finds the corresponding index on the spool,
1132 if any. Returns 0 if not found.
1135 find_index (ModeInfo *mi, flapper *f, long uc)
1138 int L = utf8_encode (uc, string, sizeof(string) - 1);
1140 if (L <= 0) return 0;
1142 for (i = 0; i < f->spool_size; i++)
1144 if (!strcmp (string, f->spool[i]))
1151 /* Read input from the text client and populate the spool with it.
1154 fill_targets (ModeInfo *mi)
1156 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
1163 time_t now = time ((time_t *) 0);
1164 struct tm *tm = localtime (&now);
1165 const char *fmt = (bp->clock_p == 24
1169 strftime (buf, sizeof(buf)-1, fmt, tm);
1170 if (bp->clock_p == 12 && buf[0] == '0')
1173 for (i = 0; i < strlen(buf); i++)
1175 flapper *f = &bp->flappers[i];
1176 f->target_index = find_index (mi, f, buf[i]);
1178 for (; i < grid_width * grid_height; i++)
1180 flapper *f = &bp->flappers[i];
1181 f->target_index = find_index (mi, f, ' ');
1186 for (y = 0; y < grid_height; y++)
1189 for (x = 0; x < grid_width; x++)
1191 int i = y * grid_width + x;
1192 flapper *f = &bp->flappers[i];
1193 unsigned long uc = ((nl_p || cls_p) ? ' ' : read_unicode (mi));
1194 if (uc == '\r' || uc == '\n')
1196 else if (uc == 12) /* ^L */
1199 /* Convert Unicode to the closest Latin1 equivalent. */
1202 Bool ascii_p = (f->spool != latin1_spool);
1203 unsigned char s[5], *s2;
1204 int L = utf8_encode (uc, (char *) s, sizeof(s));
1206 s2 = (unsigned char *) utf8_to_latin1 ((char *) s, ascii_p);
1208 if (s2[0] < 128) /* ASCII */
1210 else /* Latin1 -> UTF8 -> Unicode */
1212 s[0] = (s2[0] > 0xBF ? 0xC3 : 0xC2);
1213 s[1] = s2[0] & (s2[0] > 0xBF ? 0xBF : 0xFF);
1215 utf8_decode (s, 2, &uc);
1221 /* Upcase ASCII. Upcasing Unicrud would be rocket surgery. */
1222 if (uc >= 'a' && uc <= 'z') uc += ('A'-'a');
1224 f->target_index = find_index (mi, f, uc);
1226 f->sticky = (((random() % 20) == 0)
1227 ? 0.05 + frand(0.1) + frand(0.1)
1233 for (y = 0; y < grid_height; y++)
1235 fprintf (stderr, "# ");
1236 for (x = 0; x < grid_width; x++)
1238 int i = y * grid_width + x;
1239 flapper *f = &bp->flappers[i];
1240 fprintf(stderr, "%s", bp->spool[f->target_index]);
1242 fprintf (stderr, " #\n");
1244 fprintf (stderr, "\n");
1250 draw_flappers (ModeInfo *mi, Bool text_p)
1252 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
1256 for (y = 0; y < grid_height; y++)
1257 for (x = 0; x < grid_width; x++)
1259 int i = (grid_height - y - 1) * grid_width + x;
1260 flapper *f = &bp->flappers[i];
1266 if (x >= 2) xx += COLON_WIDTH;
1267 if (x >= 4) xx += COLON_WIDTH;
1268 if (x >= 6) xx += COLON_WIDTH;
1275 glTranslatef (xx, yy, 0);
1276 mi->polygon_count += draw_flapper (mi, f, text_p);
1278 if (text_p && bp->clock_p && (x == 2 || x == 4))
1279 mi->polygon_count += draw_colon (mi);
1285 tick_flapper (mi, f);
1286 if (f->current_index != f->target_index)
1291 if (text_p && !running)
1295 else if (bp->linger)
1303 /* Base of 1 second, plus 1 second for every 25 characters.
1304 Also multiply by speed? */
1306 if (!bp->first_time_p)
1307 bp->linger += (grid_width * grid_height * 1.2);
1308 bp->first_time_p = False;
1315 draw_splitflap (ModeInfo *mi)
1317 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
1318 Display *dpy = MI_DISPLAY(mi);
1319 Window window = MI_WINDOW(mi);
1321 if (!bp->glx_context)
1324 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
1326 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1329 glRotatef(current_device_rotation(), 0, 0, 1);
1331 glScalef (0.1, 0.1, 0.1); /* because of gluLookAt */
1335 get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
1336 glTranslatef((x - 0.5) * 8,
1340 gltrackball_rotate (bp->trackball);
1347 get_position (bp->rot2, &x, &y, &z, !bp->button_down_p);
1348 if (bp->spinx) glRotatef (maxy/2 - x*maxy, 1, 0, 0);
1349 if (bp->spiny) glRotatef (maxx/2 - y*maxx, 0, 1, 0);
1350 if (bp->spinz) glRotatef (maxz/2 - z*maxz, 0, 0, 1);
1354 get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
1355 glRotatef (x * 360, 1, 0, 0);
1356 glRotatef (y * 360, 0, 1, 0);
1357 glRotatef (z * 360, 0, 0, 1);
1361 /* Fit the whole grid on the screen */
1363 GLfloat r = MI_HEIGHT(mi) / (GLfloat) MI_WIDTH(mi);
1364 int cells = (grid_width > grid_height
1369 s *= 2; /* #### What. Why is this necessary? */
1375 mi->polygon_count = 0;
1376 mi->polygon_count += draw_component (mi, SPLITFLAP_OUTER_FRAME);
1379 GLfloat xoff = (bp->clock_p == 12 ? COLON_WIDTH * 3 :
1380 bp->clock_p == 24 ? COLON_WIDTH * 2 :
1382 glTranslatef (1 - (grid_width + xoff), 1 - grid_height, 0);
1385 /* We must render all text after all polygons, or alpha blending
1386 doesn't work right. */
1387 draw_flappers (mi, False);
1388 draw_flappers (mi, True);
1392 if (mi->fps_p) do_fps (mi);
1395 glXSwapBuffers(dpy, window);
1399 free_splitflap (ModeInfo *mi)
1401 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
1403 textclient_close (bp->tc);
1404 /* #### bp->texinfo */
1407 XSCREENSAVER_MODULE ("SplitFlap", splitflap)